$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";
}
*/
#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
的类)构成继承协定,则这些子类可能不会改变继承方法的签名,这意味着预期的类型(类型提示)可能并不严格大于或不同于父代中定义的类型(即:不允许array
与stdClass
)。允许其他参数,只要它们是可选的即可。如果
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_MODE
和ATTR_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
评论
您实际上使情况变得更糟!这时,您将摆脱PDO最强大的功能之一:准备好的语句。您可以使用不同的变量多次执行准备好的语句。但是您的功能不允许这样做。扩展/包装一个类应该添加功能,而不是删除它@Pinoniq:此类执行的每个查询都使用准备好的语句AFAIKT
将PDO包装成“更好”的东西是不可能的。 PDO尽其所能。您所做的使情况变得更糟。如果有人已经知道SQL,为什么有人会学习您的用于插入/删除/更新的类方法?我永远不会使用$ obj-> insertRow()或任何类似的东西。我将使用实现ActiveRecord模式的类,该类完全消除了插入/删除/更新命令的存在。不要以错误的方式使用它,但要检查ActiveRecord和对象关系映射器。
@EliasVanOotegem关于提供这些stmt的数据库是正确的,但是如果它不允许我们访问它们,您的代码有什么用。我只是简单地指出一句话(他因为-> prepare()函数而使用了准备好的状态菜单,对我来说就像是“由于关键字类而在写OO”一样。如果我冒犯了您,我深表歉意...我不是早上的类型;)
我正在阅读此问题,并大笑着哭。