PDO使用localhost作为主机地址连接MySQL8.1服务器的坑

<?php
declare(strict_types=1);
ini_set('display_errors', 'On');
error_reporting(-1);

/**
 * PDOi类
 */
class PDOi
{
    private ?PDO $pdo = null;

    private static ?PDOi $instance = null;

    /**
     * 将构造方法声明为private防止通过“new PDOi()”创建实例
     */
    private function __construct()
    {
    }

    /**
     * 将克隆方法声明为private防止复制实例
     */
    private function __clone()
    {
    }

    /**
     * 获取PDOi实例
     *
     * @return PDOi PDOi实例
     */
    private static function getInstance(): PDOi
    {
        if (self::$instance === null) {
            self::$instance = new self();

            $driver   = 'mysql';     // 数据库驱动
            $host     = 'localhost'; // 主机地址,可选值:localhost | 192.168.*.* | 127.0.0.1
            $dbname   = 'test_db';   // 数据库名
            $port     = 3308;        // 服务端口号
            $charset  = 'utf8mb4';   // 字符编码
            $username = 'ZhangSan';  // 账号
            $password = '********';  // 密码

            // 构造数据源名称(Data Source Name)字符串
            $dsn = "$driver:";
            $dsn .= "host=$host:$port;"; // 重要提醒:由于主机地址为localhost,必须使用“localhost:$port”的格式。
            $dsn .= "dbname=$dbname;";
            // $dsn .= "port=$port;"; // 重要提醒:由于主机地址为localhost,导致port参数失效,必须在host参数就指定port。
            $dsn .= "charset=$charset;";

            // PDO连接选项
            $options = [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES $charset",
                PDO::ATTR_EMULATE_PREPARES => false,
            ];

            // 创建PDO类实例
            try {
                self::$instance->pdo = new PDO($dsn, $username, $password, $options);
            } catch (PDOException $e) {
                exit('创建PDO实例失败:' . $e->getMessage());
            }
        }

        return self::$instance;
    }

    /**
     * 获取数据库版本号
     *
     * @return string 数据库版本号
     */
    public static function version(): string
    {
        try {
            $version = (string)self::getInstance()->pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
        } catch (PDOException $e) {
            $version = $e->getMessage();
        }

        return $version;
    }

    /**
     * 执行语句
     *
     * @param string $query 要执行的SQL语句模板
     * @param array $args 模板参数
     * @return PDOStatement|false 执行成功返回PDOStatement实例,否则返回false
     */
    public static function execute(string $query, array $args = []): PDOStatement|false
    {
        // 预处理要执行的语句
        try {
            $sth = self::getInstance()->pdo->prepare($query);
        } catch (PDOException $e) {
            exit('预处理要执行的语句失败:' . $e->getMessage());
        }

        // 绑定参数
        if ($sth instanceof PDOStatement) {
            foreach ($args as $field => $value) {
                try {
                    $sth->bindValue(":$field", $value);
                } catch (PDOException $e) {
                    exit('绑定参数失败:' . $e->getMessage());
                }
            }
        }

        // 执行语句
        if ($sth instanceof PDOStatement && $sth->execute() === false) {
            $sth = false;
        }

        return $sth;
    }
}

echo '当前数据库版本号:' . PDOi::version() . PHP_EOL; // 当前数据库版本号:8.1.0

$query = 'SELECT * FROM `prefix_article` WHERE `aid` = :aid LIMIT 1';
$args = ['aid' => 2];

$sth = PDOi::execute($query, $args);
if ($sth instanceof PDOStatement) {
    $fetchAll = $sth->fetchAll();
    $row = $fetchAll[0] ?? [];
    if ($row) {
        echo "({$row['aid']}) {$row['title']} [点击:{$row['click']}]"; // (2)  PHP是世界上最好の语言 [点击:9527]
    } else {
        echo '没有符合查询条件的数据';
    }
}


//========== 总结 ==========//
// 1、使用PDO连接MySQL8.1服务器时,如果主机地址为localhost,那么DSN的port参数会失效,必须使用“host=localhost:$port”才能正常连接,
//    注意这只针对主机地址为localhost的情况,如果主机地址为IP地址(如:192.168.*.* 或 127.0.0.1),那么DSN的port参数依然有效,不需
//    要在host参数里就指定port。
// 2、对于“host=$host:$port”这种写法,不管MySQL服务器使用哪个版本,也不管$host使用localhost还是IP地址,该写法都是有效的,所以最安
//    全的办法就是统一使用“host=$host:$port”,DSN不再额外使用port参数指定端口号。

Copyright © 2024 码农人生. All Rights Reserved