【问题标题】:Passing PDO object into class - PHP Fatal error: Call to a member function execute() on a non-object将 PDO 对象传递给类 - PHP 致命错误:在非对象上调用成员函数 execute()
【发布时间】:2016-03-03 00:18:15
【问题描述】:

我正在重构一个我之前写过的(过程)PHP 库,使其成为一个轻量级的 OOP 框架。我正忙于尝试传递要在类中使用的 PDO 对象。这是我到目前为止所得到的。

Config.php

<?php

class Config {
    // Database Variables
    private $db_type;
    private $db_host;
    private $db_user;
    private $db_pass;
    private $db_name;
    private $db_path; // for sqlite database path
    private $db_char; // charset

    // Site Variables
    private $s_protocol;
    private $s_subdomain;
    private $s_domain;
    private $s_tld;
    private $s_dir;
    private $s_name;
    private $s_description;
    private $s_path;
    private $s_visibility;
    private $s_pipe;
    private $s_apps;
    private $s_hooks;
    private $s_blocks;
    private $s_assets;

    // User Default
    private $u_groupid;

    public function __construct($config) {
        $this->set($config);
    }

    public function set($config) {
        if (!empty($config) && is_array($config)) {
            foreach ($config as $k => $v) {
                if (property_exists(get_class($this), $k)) {
                    $this->$k = $v;
                }
            }
            return true;
        }
        else { return false; }
    }

    public function get($config) {
        if (!empty($config)) {
            return $this->$config;
        }
    }

    public function domain() {
        return $this->get('s_protocol') .'://'. $this->get('s_domain') . $this->get('s_tld') .'/'. $this->get('s_dir');
    }
}
?>

数据库.php

<?php

class Database extends PDO {
    private $config;

    public function __construct($config) {
        $this->config = $config;
        switch($this->config->get('db_type')) {
            case 'mysql':
            case 'pgsql':
                try {
                    return new PDO(
                                $this->config->get('db_type') .':dbname='. $this->config->get('db_name') .';host='. $this->config->get('db_host'),
                                $this->config->get('db_user'),
                                $this->config->get('db_pass')
                    );
                }
                catch(PDOException $e) {
                    die($e->getMessage());
                }
                break;
            case 'sqlite':
                try {
                    return new PDO($this->config->get('db_type') .':'. $this->config->get('db_path'));
                }
                catch(PDOException $e) {
                    die($e->getMessage());
                }
                break;
            case 'firebird':
                try {
                    return new PDO(
                                $this->config->get('db_type') .':dbname='. $this->config->get('db_host') .':'. $this->config->get('db_path'),
                                $this->config->get('db_user'),
                                $this->config->get('db_pass')
                    );
                }
                catch(PDOException $e) {
                    die($e->getMessage());
                }
                break;
            case 'informix':
                try {
                    return new PDO(
                                $this->config->get('db_type') .':DSN='. $this->config->get('db_name'),
                                $this->config->get('db_user'),
                                $this->config->get('db_pass')
                    );
                }
                catch(PDOException $e) {
                    die($e->getMessage());
                }
                break;
            case 'oracle':
                try {
                    return new PDO(
                                'OCI:dbname='. $this->config->get('db_name') .';charset='. $this->config->get('db_char'),
                                $this->config->get('db_user'),
                                $this->config->get('db_pass')
                    );
                }
                catch(PDOException $e) {
                    die($e->getMessage());
                }
                break;
        }
    }


}
?>

Auth.php

<?php

class Auth {
    // Set Database object
    protected $db;

    // User fields in users table
    private $id;
    private $email;
    private $password;
    private $firstname;
    private $lastname;
    private $displayname;
    private $groupid;
    private $ip;
    private $created;
    private $updated;
    private $cookie;
    private $sessionid;
    private $lastlogin;
    private $token;
    private $active;

    public function __construct($dbh) {
        $this->db = $dbh;
    }

    public function add($params) {
        $sql = '
            INSERT INTO
                `users` (
        ';
        $cols = array_keys($params);
        $col_string = implode(', ', $cols);
        $sql .= $col_string .'
                )
            VALUES (
        ';
        array_walk($cols, function(&$v, $k) { $v = ':'. $v; });
        $col_string = implode(', ', $cols);

        $sql .= $col_string .'
                )
        ';
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);

    }

    public function remove($params) {

    }

    public function update($params) {

    }

    public function get($params) {

    }

    protected function set($params) {
        if (!empty($params) && is_array($params)) {
            foreach ($params as $k => $v) {
                if (property_exists(get_class($this), $k)) {
                    $this->$k = $v;
                }
            }
            return true;
        }
        else { return false; }
    }
}

?>

init.php

<?php
session_start();
$params = array(
                'db_type' => 'mysql',
                'db_host' => '127.0.0.1',
                'db_user' => 'user',
                'db_pass' => 'password',
                'db_name' => 'database',
                'u_groupid' => 4
            );
require_once('Config.php');         $c = new Config($params);
require_once('Database.php');       $db = new Database($c);
require_once('Auth.php');           $u = new Auth($db);

$user = array(
    'email' => 'newperson@email.com',
    'password' => md5('password'),
    'firstname' => 'Jeff',
    'lastname' => 'Wilson',
    'displayname' => 'Jeff Wilson',
    'groupid' => $c->get('u_groupid'),
    'ip' => $_SERVER['REMOTE_ADDR'],
    'created' => date('Y-m-d H:i:s'),
    'sessionid' => session_id(),
    'active' => 1,
);
$u->add($user);
?>

PHP 致命错误:在第 46 行对 Auth.php 中的非对象调用成员函数 execute()

这是第 46 行: $stmt-&gt;execute($params);

据我所知,我正确地将 PDO 对象传递给 Auth 类。它不应该说它是一个非对象。其他人可以看到这里有什么问题吗?

【问题讨论】:

  • 看起来 $this-&gt;db-&gt;prepare($sql) 失败并且 pdo 实例未设置为 ERRMODE_EXCEPTION -> $stmt "is" FALSE。
  • @VolkerK - 我已经添加了这些设置,页面仍然抛出 500 Internal Server Error 并检查 http 日志它仍然说同样的事情 - PHP 致命错误:调用成员函数 execute()在第 46 行的 Auth.php 中的非对象上。

标签: php mysql oop pdo


【解决方案1】:

除非 pdo 实例被明确设置为使用异常报告错误,否则您必须检查 PDO::prepare 的返回值

$stmt = $this->db->prepare($sql);
if ( !$stmt ) {
    // prepare failed
    // the array returned by $this->db->errorinfo() most likely contains more info about the error
    // don't send it unconditionally,directly to the browser, see https://www.owasp.org/index.php/Top_10_2013-A6-Sensitive_Data_Exposure

}
else {
    $result = $stmt->execute($params);
    if ( !$result ) {
        // not ok
    }
    else {
        // ok
    }
}

编辑:我可以省略我答案的第一部分,因为 Alex Ivey 已经 wrote it。 ;-)

只是图像背后的场景 php 正在做类似的事情

function __internal_newDatabase() {
    // just image the parser/compiler creates this function
    // from your class defintion
    $instance = array(
        'properties'=>array('config'=>null),
        'methods'=>array('beginTransaction'=>PDO::beginTransaction, 'prepare'=>PDO::prepare, ...)
    );  
    // some magic function that calls a method and "replaces" $this by the second parameter
    invoke(Database::__construct, $instance);
    return $instance;
}

当你的脚本包含new Database 时,它会调用__internal_newDatabase()。这就是(ooooversimplified)发生的事情,因此您可以通过返回不同的实例来“更改”构造函数“方法”中的实例。您的构造函数应该使 this 实例飞行(或通过抛出异常来退出)。
您的类数据库派生自 PDO,即它应该表现为 PDO。在其他语言中,这意味着必须调用/a 基类的构造函数。 PHP 不强制执行此操作。但在这种情况下,您的数据库实例将无法使用。正如亚历克斯的回答所示,您必须明确地调用父母的构造函数。

但是这个类还有其他问题。 (首先坦白:我有偏见,可能反对class Database几乎所有在所有情况下都是错误的,因此自动为我提出了一个危险信号)
最重要的是:鉴于它的名称和您使用它的方式,它是多余的。它只是一个配置细节,而不是从 PDO 派生的类。更有可能是工厂和/或 IoC 容器中的某物(如果您使用它的话)。
另一方面,它可能不仅仅是一个配置细节,而且可能(并且可能会)导致不同数据库的不同实现。 PDO 不是数据库抽象,只是一个统一的访问层。
您的 Auth.php 类不关心所使用的具体 sql 方言 - 这个特定的查询很可能适用于 PDO 支持的所有数据库系统。但是迟早会有必须为不同的 RDBMS 定制的查询。然后你的基类可能会被称为DB_Adapter 并且会有MySQL_Adapter extends DB_Adapter 等等......

【讨论】:

  • 当我把它放进去时,它并没有给我一个 500 Internal Server Error - 但是检查它抛出的 http 日志: PHP Notice: Undefined property: Database::$errorinfo
  • errorinfo 不是属性,而是返回数组的方法。见docs.php.net/pdo.errorinfo
  • 啊,好吧!我很抱歉。这是 http 日志中返回的内容 - PHP 警告:PDO::errorInfo(): SQLSTATE[00000]: No error: PDO constructor was not called
  • 哦,现在问题变得有趣了 ;-) ...因为我只浏览了代码。您的类 Database 扩展了 PDO 并且当您调用 $this->prepare() 它实际上调用 PDO::prepare 但 this 实例尚未正确初始化(...因为您可以实例化派生类而不调用基类的任何构造函数;我不太喜欢 php 的“功能”之一)。我将编辑答案,但总结是:Database::__construct 不应该做类似return new PDO(... 的事情,它已经在处理要初始化的实例。
【解决方案2】:

将此方法添加到您的数据库类

   public function getConnection() {
        return new PDO(
            $this->config->get('db_type') .':dbname='. $this->config->get('db_name') .';host='. $this->config->get('db_host'),
            $this->config->get('db_user'),
            $this->config->get('db_pass')
        );
    }

像这样调用prepare语句:

$conn = $this->db->getConnection();
$stmt = $conn->prepare($sql);

在这种情况下,您的 $conn 必须是 PDO 对象

这里的主要问题是虽然$db = new Database($c); 乍一看似乎很好,但调用$db-&gt;prepare 并不好,因为$dbDatabase 的实例,但必须是PDO 的实例

改进位连接处理的方法之一是: 在你的Database 类中拥有一个private $conn 用于连接

class Database extends PDO {
    private $config;
    private $conn;

     public function __construct($config) {
        $this->config = $config;
        switch($this->config->get('db_type')) {
            case 'mysql':
            case 'pgsql':
               try {
                    $this->conn = new PDO(
                        $this->config->get('db_type') . ':dbname=' . $this->config->get('db_name') . ';host=' . $this->config->get('db_host'),
                        $this->config->get('db_user'),
                        $this->config->get('db_pass')
                    );
                } catch(PDOException $e) {
                    die($e->getMessage());
                }

            break;

         // ...

        }
    }

THEN 在同一个类中新方法返回连接:

public function getConnection() {
        return $this->conn;
    }

最后调用它:

$this->db->getConnection()->prepare($sql)

【讨论】:

    【解决方案3】:

    Database 类中的构造函数正在返回一个值(一个 PDO 对象)。 __construct() 函数不应显式返回值。由于您的数据库类扩展了 PDO,请改为调用父构造函数:

    parent::__construct(
        $this->config->get('db_type') .':dbname='. $this->config->get('db_name') .';host='. $this->config->get('db_host'),
        $this->config->get('db_user'),
        $this->config->get('db_pass')
    );
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-30
      • 1970-01-01
      • 2015-06-14
      • 1970-01-01
      • 2012-09-01
      • 2016-02-12
      相关资源
      最近更新 更多