同意,那里有许多PHP PDO类。但是我发现它们不允许灵活性。因此,我创建了一个可以帮助减少开发时间的方法,但是它确实可以完成工作(也许与断开连接部分无关,但是它可以跟踪数据库是否通过$database->isConnected连接)。您能指出任何缺陷和可能的改进吗?

<?php 
    class db
    {
        public $isConnected;
        protected $datab;
        public function __construct($username, $password, $host, $dbname, $options=array()){
            $this->isConnected = true;
            try { 
                $this->datab = new PDO("mysql:host={$host};dbname={$dbname};charset=utf8", $username, $password, $options); 
                $this->datab->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
                $this->datab->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
            } 
            catch(PDOException $e) { 
                $this->isConnected = false;
                throw new Exception($e->getMessage());
            }
        }
        public function Disconnect(){
            $this->datab = null;
            $this->isConnected = false;
        }
        public function getRow($query, $params=array()){
            try{ 
                $stmt = $this->datab->prepare($query); 
                $stmt->execute($params);
                return $stmt->fetch();  
                }catch(PDOException $e){
                throw new Exception($e->getMessage());
            }
        }
        public function getRows($query, $params=array()){
            try{ 
                $stmt = $this->datab->prepare($query); 
                $stmt->execute($params);
                return $stmt->fetchAll();       
                }catch(PDOException $e){
                throw new Exception($e->getMessage());
            }       
        }
        public function insertRow($query, $params){
            try{ 
                $stmt = $this->datab->prepare($query); 
                $stmt->execute($params);
                }catch(PDOException $e){
                throw new Exception($e->getMessage());
            }           
        }
        public function updateRow($query, $params){
            return $this->insertRow($query, $params);
        }
        public function deleteRow($query, $params){
            return $this->insertRow($query, $params);
        }
    }
    //USAGE 
    /*      
        Connecting to DataBase
        $database = new db("root", "", "localhost", "database", array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));

        Getting row
        $getrow = $database->getRow("SELECT email, username FROM users WHERE username =?", array("yusaf"));

        Getting multiple rows
        $getrows = $database->getRows("SELECT id, username FROM users");

        inserting a row
        $insertrow = $database ->insertRow("INSERT INTO users (username, email) VALUES (?, ?)", array("yusaf", "yusaf@email.com"));

        updating existing row           
        $updaterow = $database->updateRow("UPDATE users SET username = ?, email = ? WHERE id = ?", array("yusafk", "yusafk@email.com", "1"));

        delete a row
        $deleterow = $database->deleteRow("DELETE FROM users WHERE id = ?", array("1"));
        disconnecting from database
        $database->Disconnect();

        checking if database is connected
        if($database->isConnected){
        echo "you are connected to the database";
        }else{
        echo "you are not connected to the database";
        }

    */


评论

您实际上使情况变得更糟!这时,您将摆脱PDO最强大的功能之一:准备好的语句。您可以使用不同的变量多次执行准备好的语句。但是您的功能不允许这样做。扩展/包装一个类应该添加功能,而不是删除它

@Pinoniq:此类执行的每个查询都使用准备好的语句AFAIKT

将PDO包装成“更好”的东西是不可能的。 PDO尽其所能。您所做的使情况变得更糟。如果有人已经知道SQL,为什么有人会学习您的用于插入/删除/更新的类方法?我永远不会使用$ obj-> insertRow()或任何类似的东西。我将使用实现ActiveRecord模式的类,该类完全消除了插入/删除/更新命令的存在。不要以错误的方式使用它,但要检查ActiveRecord和对象关系映射器。

@EliasVanOotegem关于提供这些stmt的数据库是正确的,但是如果它不允许我们访问它们,您的代码有什么用。我只是简单地指出一句话(他因为-> prepare()函数而使用了准备好的状态菜单,对我来说就像是“由于关键字类而在写OO”一样。如果我冒犯了您,我深表歉意...我不是早上的类型;)

我正在阅读此问题,并大笑着哭。

#1 楼

就我个人而言,我必须说,到目前为止,我所见过的几乎所有PDO派生/基于类都遭受相同的问题:从本质上讲,它们是毫无意义的。
让我清楚:PDO提供了一个清晰,易于使用的方法自行维护和整洁的接口,将其包装在自定义类中以更好地满足特定项目的需求,本质上是采用了不错的OO工具,并且对其进行了更改,以使您无法在其他项目中轻松地重用它。如果您要编写有关MySQLi的包装,我可以理解其原因,但是PDO吗?不,除非您:
要编写一个与之配套的表映射器类,以便在您查询的表与存储的数据模型之间建立连接,否则我真的很难理解它的逻辑。数据。就像Zend\Db的工作原理一样。
众所周知,MySQL在数据类型方面不如PHP灵活。如果要编写DB抽象层,则常识表明该层反映了这一点:它应该使用强制转换,常量,过滤器以及准备好的语句来反映这一点。
那里最成熟的代码还提供了一个API不需要您编写自己的查询:
$query = $db->select('table')
            ->fields(array('user', 'role', 'last_active')
            ->where('hash = ?', $pwdHash);

这些抽象层通常(如果不是总是)提供了另一个好处,它们根据您连接的数据库为您构建查询。如果您使用mysql,他们将构建一个MySQL查询,如果您碰巧切换到PostgreSQL,他们将淘汰pg查询,而无需重写数千个查询。如果要保留并编写自己的抽象层,请确保也提供类似的内容。如果您不这样做,那么您将回到正题:开始一项劳动密集型,毫无意义的冒险,这是不值得的。
另一种方法是扩展PDO类。再一次,这是以前完成的,从理论上讲完全可以。尽管这又可能是个人的,但它确实违反了我认识的许多开发人员所坚持的一项原则:不要扩展或尝试更改您不拥有的对象。 PDO是一个核心对象,因此很明显您不拥有它。
假设您要编写类似以下内容的代码:
class MyDO extends PDO
{
    public function createProcedure(array $arguments, $body)
    {
        //create SP on MySQL server, and return
    }
}

并假设经过一些认真的调试和测试, ,您实际上可以正常工作。大!但是,如果将来某个时候PDO有自己的createProcedure方法怎么办?它可能会胜过您的产品,并且功能可能会更强大。那本身不是问题,但是假设它的签名也不同:
public function createProcedure (stdClass $arguments)
{
}

这意味着您要么必须放弃方法,然后重构整个代码库以使用请调整PDO的最新和最佳匹配,否则您必须将方法更改为:
public function createProcedure(array $arguments, $body)
{
    $arguments = (object) $arguments;//create stdClass
    //parse $body and:
    $arguments->declare = $body[0];
    $arguments->loops = (object) array('loop1' => $body[1], 'loop2' => $body[2]);
    $arguments->body = (object) array(
        'main' => $body[3],
        'loop1_body' => $body[4],
        'loop2_body' => $body[5]
    );
    return parent::createProcedure($arguments);
}

这意味着,对于您编写的所有代码,实际上必须调用2个方法,将您曾经聪明的createProcedure方法转化为自重。那你可能会说什么呢?好吧,不要以为您还走出困境,因为上面的另一种方法是非法的,它不能被编写,不能工作,不应该工作,只是种种错误,原因如下:
Liskov原理指出,如果子类(扩展为PDO的类)构成继承协定,则这些子类可能不会改变继承方法的签名,这意味着预期的类型(类型提示)可能并不严格大于或不同于父代中定义的类型(即:不允许arraystdClass)。允许其他参数,只要它们是可选的即可。
如果PDO方法本身只接受一个stdClass类型的参数,则您的子类只能添加可选参数,并且应该删除该类型提示或坚持使用(即:提示stdClass,这将破坏所有现有代码) ),或者根本不暗示(这很容易出错)。
再过几个月,人们可能会使用依赖于createProcedure方法的第三方代码(框架) ,并将其传递给stdClass的实例。您必须再次将方法更改为模糊且容易出错的签名:
public function createProcedure($arrOrObject, $body = null)
{
    if ($arrOrObject instanceof stdClass)
    {
        return parent::createProcedure($arrOrObject);
    }
    if ($body === null)
    {
        //What now??
    }
    //parse body
}

如果$body为空,并且$arrOrObject是一个数组,则用户可能已经在同一数组中构造了$arrOrObject数组就像PDO希望看到对象结构化的方式一样,在这种情况下json_decode(json_encode($arrOrObject));可以解决问题(不是强制转换,因为强制转换不会强制递归),但是调用您的方法的代码包含错误的可能性就很高。该怎么办?转换为对象,然后转换为try-catch,这可能会导致额外的开销?
这导致我到了最后一个,也是目前最大的遗漏:
使用包装对象时,通常是一个好主意一个(缓慢的)魔术__call方法,该方法检查方法调用是针对包装对象还是针对包装对象。
使用您的对象,我可能想在PDO扩展上设置另一个属性,但是由于您无法实现setAttribute方法,因此无法更改字符集,也无法更改PDO处理NULL值的方式。这只能被认为是明显的遗漏。特别是因为您希望用户将裸PDO常量传递给构造函数。基本上,您至少应该做的是添加:
public function __call($method, array $arguments )
{
    return call_user_func_array($this->datab, $arguments);
}

这样,您可以将实际包装的对象半公开给用户,从某种意义上说,尚未实现的方法仍然可以使用。将来可能会实施的新方法也将自动可用。
此外,您将能够验证/检查参数并记录用户使用的方法,以便您可以微调您的类以更好地反映其使用方式。
概述:

恕我直言,在像PDO这样的用户友好的原始API上构建抽象类,就像折断的铅笔一样:无意义的
扩展PDO意味着您不必创建传递方法来调用包装的API(例如创建prepare方法),但这确实意味着每当PDO更改时就有机会重构代码
如果您仍然想使用包装对象,请考虑将其包装有些事情鲜为人知,但在某些方面要好一些(我要指出的是mysqli_*),但是要实现魔术__call,如果需要的话可以实现__callStatic方法。

同样,如果要继续的话:在整个抽象层上工作,允许用户将表映射到数据模型,然后从那里获取数据。确保可以轻松地使用这些数据模型来构建HTML表单,例如,以便眨眼之间就能将这些表单链接到数据库,机器人当然也不要忘记清理数据。

关于代码本身:
首先,您必须解决有关代码的一件事,那就是您的构造函数:
我可能选择像这样构造一个对象:
$database = new db("user", "pwd", "host", "mydb",
             array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ));

设置默认的获取模式以获取对象,并且新的PDO实例将传递正确的属性数组。可悲的是,在构造完该PDO对象之后,您决定将默认的访存模式设置为PDO::FETCH_ASSOC,因为再往下两行:
$this->datab->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

我不想使用该代码。另外,这不会累加起来:
try
{ 
    $this->datab = new PDO("mysql:host={$host};dbname={$dbname};charset=utf8", $username, $password, $options); 
    $this->datab->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
    $this->datab->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} 
catch(PDOException $e)
{ 
    $this->isConnected = false;
    throw new Exception($e->getMessage());
}

尝试抓投掷?为什么?连接失败,PDOException告诉我为什么,这就是我想知道的原因,为什么要捕获该异常并抛出一个新的,更通用的异常?如果我将PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT传递给构造函数,但连接失败怎么办?您最好将其替换为:
$this->datab = new PDO($connString, array(
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'//careful with this one, though
));
foreach($options as $attr => $value)
{
    $this->datab->setAttribute($attr, $value);
}

让异常消失。如果与数据库的连接失败,则脚本无论如何都无法执行应做的事情。在使用事务的情况下,PDOExceptions非常有用。如果999个查询成功,但第1000个查询失败,那么您要做的不是插入部分数据,捕获异常并回滚事务,而是捕获异常以将其重新抛出只是愚蠢的。
但是,再次,我不会阻止您去做您想做的事情,也许您可​​以证明我做错了,实际上做了一些很棒的事情。但是要做到这一点,您必须知道已经存在的内容:

和理论一样,Wiki是开始ORM Wiki的好地方
>
ActiveRecord模式Wiki




主义

及其所有项目

>至少不是,它的ORM




请不要使用它,但是请注意推荐它
PHPActiveRecord

Zend \ Db \ Adapter和Zend\Db命名空间中的所有组件
即使是旧的Zend_Db仍然可以解决问题


评论


\ $ \ begingroup \ $
@YusafKhaliq:如果您将格式错误的查询字符串作为参数传递,我理解您在捕获错误之后的想法,但您不应该这样做。如果捕获该异常并尝试修复错误,那么格式错误的查询将无法修复,并且每次执行该段代码时,都会降低系统速度。如果查询格式错误,那就是一个错误:不要解决它,请解决它。
\ $ \ endgroup \ $
– Elias Van Ootegem
13年8月6日在6:49

#2 楼

有点旧的帖子,但是我不得不在看到这一行的地方添加一些内容

$this->datab->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
$this->datab->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);


在我看来,您像在对设置进行硬编码行:

$this->datab = new PDO("mysql:host={$host};dbname={$dbname};charset=utf8", $username, $password, $options); 


这意味着,无论用户(在与ATTR_MODEATTR_DEFAULT_MODE相关的情况下)在构造函数中设置为$options,都将被setAttribute方法覆盖。我认为这似乎毫无意义。我建议将构造函数更改为以下内容:

...

private $defaultPdoAttr = [
    \PDO::ATTR_EMULATE_PREPARES => FALSE,
    \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
    \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC
];

public function __construct($dsn, $username, $password, array $driverOptions = [])
{
    if (!$driverOptions) {
        $driverOptions = $this->defaultPdoAttr;
    }  

    $this->datab = new PDO("mysql:host={$host};dbname={$dbname};charset=utf8", $username, $password, $driverOptions );

...


评论


\ $ \ begingroup \ $
我并不是说您是故意这样做的,但是您基本上已经重复了我在回答中所说的(我在“关于代码本身”下讨论的第一件事)。 PS:如果不是空的,我不仅会重新分配$ driverOptions,还会遍历默认值,并添加未在$ driverOptions中设置的键(合并数组),类似于我在上一代码中建议的我的回答中的摘录;)
\ $ \ endgroup \ $
– Elias Van Ootegem
2015年9月10日15:56