diff --git a/system/Security/CheckPhpIni.php b/system/Security/CheckPhpIni.php
new file mode 100644
index 000000000000..282102434fae
--- /dev/null
+++ b/system/Security/CheckPhpIni.php
@@ -0,0 +1,151 @@
+
+ *
+ * For the full copyright and license information, please view
+ * the LICENSE file that was distributed with this source code.
+ */
+
+namespace CodeIgniter\Security;
+
+use CodeIgniter\CLI\CLI;
+use CodeIgniter\View\Table;
+
+/**
+ * Checks php.ini settings
+ *
+ * @used-by \CodeIgniter\Commands\Utilities\PhpIniCheck
+ * @see \CodeIgniter\Security\CheckPhpIniTest
+ */
+class CheckPhpIni
+{
+ /**
+ * @param bool $isCli Set false if you run via Web
+ *
+ * @return string|void HTML string or void in CLI
+ */
+ public static function run(bool $isCli = true)
+ {
+ $output = static::checkIni();
+
+ $thead = ['Directive', 'Global', 'Current', 'Recommended', 'Remark'];
+ $tbody = [];
+
+ // CLI
+ if ($isCli) {
+ self::outputForCli($output, $thead, $tbody);
+
+ return;
+ }
+
+ // Web
+ return self::outputForWeb($output, $thead, $tbody);
+ }
+
+ private static function outputForCli(array $output, array $thead, array $tbody): void
+ {
+ foreach ($output as $directive => $values) {
+ $current = $values['current'];
+ $notRecommended = false;
+
+ if ($values['recommended'] !== '') {
+ if ($values['recommended'] !== $values['current']) {
+ $notRecommended = true;
+ }
+
+ $current = $notRecommended
+ ? CLI::color($values['current'] === '' ? 'n/a' : $values['current'], 'red')
+ : $values['current'];
+ }
+
+ $directive = $notRecommended ? CLI::color($directive, 'red') : $directive;
+ $tbody[] = [
+ $directive, $values['global'], $current, $values['recommended'], $values['remark'],
+ ];
+ }
+
+ CLI::table($tbody, $thead);
+ }
+
+ private static function outputForWeb(array $output, array $thead, array $tbody): string
+ {
+ foreach ($output as $directive => $values) {
+ $current = $values['current'];
+ $notRecommended = false;
+
+ if ($values['recommended'] !== '') {
+ if ($values['recommended'] !== $values['current']) {
+ $notRecommended = true;
+ }
+
+ if ($values['current'] === '') {
+ $current = 'n/a';
+ }
+
+ $current = $notRecommended
+ ? '' . $current . ''
+ : $current;
+ }
+
+ $directive = $notRecommended
+ ? '' . $directive . ''
+ : $directive;
+ $tbody[] = [
+ $directive, $values['global'], $current, $values['recommended'], $values['remark'],
+ ];
+ }
+
+ $table = new Table();
+ $template = [
+ 'table_open' => '
',
+ ];
+ $table->setTemplate($template);
+
+ $table->setHeading($thead);
+
+ return '' . $table->generate($tbody) . '
';
+ }
+
+ /**
+ * @internal Used for testing purposes only.
+ * @testTag
+ */
+ public static function checkIni(): array
+ {
+ $items = [
+ 'error_reporting' => ['recommended' => '5111'],
+ 'display_errors' => ['recommended' => '0'],
+ 'display_startup_errors' => ['recommended' => '0'],
+ 'log_errors' => [],
+ 'error_log' => [],
+ 'default_charset' => ['recommended' => 'UTF-8'],
+ 'memory_limit' => ['remark' => '> post_max_size'],
+ 'post_max_size' => ['remark' => '> upload_max_filesize'],
+ 'upload_max_filesize' => ['remark' => '< post_max_size'],
+ 'request_order' => ['recommended' => 'GP'],
+ 'variables_order' => ['recommended' => 'GPCS'],
+ 'date.timezone' => ['recommended' => 'UTC'],
+ 'mbstring.language' => ['recommended' => 'neutral'],
+ ];
+
+ $output = [];
+ $ini = ini_get_all();
+
+ foreach ($items as $key => $values) {
+ $output[$key] = [
+ 'global' => $ini[$key]['global_value'],
+ 'current' => $ini[$key]['local_value'],
+ 'recommended' => $values['recommended'] ?? '',
+ 'remark' => $values['remark'] ?? '',
+ ];
+ }
+
+ // [directive => [current_value, recommended_value]]
+ return $output;
+ }
+}
diff --git a/tests/system/Security/CheckPhpIniTest.php b/tests/system/Security/CheckPhpIniTest.php
new file mode 100644
index 000000000000..847f9fd82897
--- /dev/null
+++ b/tests/system/Security/CheckPhpIniTest.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view
+ * the LICENSE file that was distributed with this source code.
+ */
+
+namespace CodeIgniter\Security;
+
+use CodeIgniter\CLI\CLI;
+use CodeIgniter\Test\CIUnitTestCase;
+use CodeIgniter\Test\Mock\MockInputOutput;
+
+/**
+ * @internal
+ *
+ * @group Others
+ */
+final class CheckPhpIniTest extends CIUnitTestCase
+{
+ public function testCheckIni()
+ {
+ $output = CheckPhpIni::checkIni();
+
+ $expected = [
+ 'global' => '',
+ 'current' => '1',
+ 'recommended' => '0',
+ 'remark' => '',
+ ];
+ $this->assertSame($expected, $output['display_errors']);
+ }
+
+ public function testRunCli()
+ {
+ // Set MockInputOutput to CLI.
+ $io = new MockInputOutput();
+ CLI::setInputOutput($io);
+
+ CheckPhpIni::run(true);
+
+ // Get the whole output string.
+ $output = $io->getOutput();
+
+ $this->assertStringContainsString('display_errors', $output);
+
+ // Remove MockInputOutput.
+ CLI::resetInputOutput();
+ }
+
+ public function testRunWeb()
+ {
+ $output = CheckPhpIni::run(false);
+
+ $this->assertStringContainsString('display_errors', $output);
+ }
+}