PHP数据库类
我正在尝试通过 PHP 中的 OOP 管理我的数据库连接和查询,但我并不擅长。我知道我正在重新发明轮子,但这就是我喜欢的方式:)
我使用三个类,包括 SQL解析器我自己还没做过。创建新连接时,我的实现返回一个对象。程序员应该通过这个数据库对象创建一个新的查询实例(每个 SQL 语句一个实例)。我的问题是:如何使我的查询类只能从数据库类调用?
我正在粘贴我的课程简历和下面的实现。请随时让我知道情况有多糟糕。谢谢!
class genc_db_parser
{
/* See at http://www.tehuber.com/article.php?story=20081016164856267
returns an array with indexed values ('select','from','where','update',...) when they are available */
}
class genc_database
{
public $db; /* The database connection */
public $signature; /* Unique signature for the connection */
public static $instances = array(); /* Array of references to connection */
public static function error($e,$sql)
{
/* Errors */
}
private static function singleton($cfg,$inst)
{
$signature = sha1(serialize($cfg));
if ( isset($cfg['host'],$cfg['user'],$cfg['pass'],$cfg['db'],$cfg['engine']) )
{
foreach ( self::$instances as $obj )
{
if ( $obj->signature == $signature )
return $obj->db;
}
try
{ $db = new PDO($cfg['engine'].':host='.$cfg['host'].';dbname='.$cfg['db'], $cfg['user'], $cfg['pass']); }
catch (PDOException $e)
{ self::error($e); }
if ( $db )
{
$t = self::$instances;
array_push($t,$inst);
return $db;
}
}
return false;
}
function __construct($cfg=array())
{
if ( isset($cfg['host'],$cfg['user'],$cfg['pass'],$cfg['db']) )
$cfg['engine'] = isset($cfg['engine']) ? $cfg['engine'] : 'mysql';
else
$cfg = array(
'host' => GEN_DB_HOST,
'user' => GEN_DB_USER,
'pass' => GEN_DB_PASS,
'db' => GEN_DATABASE,
'engine' => GEN_DB_ENGINE
);
if ( isset($cfg['host'],$cfg['user'],$cfg['pass'],$cfg['db'],$cfg['engine']) )
{
if ( $this->db = self::singleton($cfg,$this) )
{
$this->signature = sha1(serialize($cfg));
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if ( $cfg['engine'] == 'mysql' )
{
$this->db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,true);
$this->db->exec('SET CHARACTER SET utf8');
}
}
}
}
public function query($sql)
{
return new genc_query($sql,&$this);
}
}
class genc_query
{
private $sql, $conn, $db, $res, $sequences, $num;
function __construct($sql_statement,$db)
{
$sql_statement = trim($sql_statement);
if ( !empty($sql_statement) )
{
$this->sql = $sql_statement;
$this->conn = &$db;
$this->db = &$db->db;
$this->analyze();
}
}
private function analyze()
{
if ( $this->sql !== null )
{
$this->sequences = genc_db_parser::ParseString($this->sql)->getArray();
}
}
private function execute()
{
if ( $this->res === null )
{
$this->res = false;
if ( isset($this->sequences['select']) )
{
try
{ $this->res = $this->db->query($this->sql); }
catch (Exception $e)
{ genc_database::error($e,$this->sql); }
}
else
{
try
{ $this->res = $this->db->exec($this->sql); }
catch (Exception $e)
{ genc_database::error($e,$this->sql); }
}
}
return $this->res;
}
public function count()
{
if ( $this->num === null )
{
$req = false;
$this->num = false;
if ( isset($this->sequences['select']) )
{
$sql = genc_db_parser::ParseString($this->sql)->getCountQuery();
try
{ $req = $this->db->query($sql); }
catch (Exception $e)
{ genc_database::error($e,$sql); }
if ( $req )
$this->num = $req->fetchColumn();
}
}
return $this->num;
}
public function get_result()
{
if ( $this->execute() )
return $this->res;
return false;
}
public function get_row()
{
$this->execute();
if ( $this->res && isset($this->sequences['select']) )
return $this->res->fetch(PDO::FETCH_ASSOC);
return false;
}
/* Other functions working on the result... */
}
执行
/* db is the database object */
$db = new genc_database();
/* concurrent connections can be opened. However giving twice the same argument will return the same corresponding opened connection */
$db2 = new genc_database(array('host'=>'localhost','user'=>'myname','pass'=>'mypass','db'=>'mydb');
/* $db->query($sql) will create a query object ($q) attached to this database */
$q = $db->query(sprintf("
SELECT id,name,modified
FROM users
WHERE id_account = %u",
$id
));
/* $q->count() will return the number of rows returned by the query (through a COUNT), and without taking the limit into account */
echo $q->count();
/* $q->get_row will return the next row of the current recordset indexed by name */
while ( $data = $q->get_row() )
echo $data['id'].': '.$data['name'].'<br />';
/* If we do another action than a select, functions ahead will not return an error but false */
/* On other actions, just to execute the query, use get_result(), which will return the number of affected rows */
$p = $db2->query("UPDATE user2 SET modified = NOW() WHERE id = 1");
echo $p->get_result().'<br />';
I am trying to manage my database connections and queries through OOP in PHP, and I'm not great at it. I know I'm reinventing the wheel, but that's the way I like it :)
I am using three classes, including a SQL parser I haven't done myself. My implementation returns an object when a new connection is created. Programmer should create a new query instance (one instance per SQL statement) through this database object. My question is: how can I get my query class to be only invocable from the database class?
I'm pasting a resume of my classes and the implementation below. Feel free to let me know how bad it is. Thanks!
class genc_db_parser
{
/* See at http://www.tehuber.com/article.php?story=20081016164856267
returns an array with indexed values ('select','from','where','update',...) when they are available */
}
class genc_database
{
public $db; /* The database connection */
public $signature; /* Unique signature for the connection */
public static $instances = array(); /* Array of references to connection */
public static function error($e,$sql)
{
/* Errors */
}
private static function singleton($cfg,$inst)
{
$signature = sha1(serialize($cfg));
if ( isset($cfg['host'],$cfg['user'],$cfg['pass'],$cfg['db'],$cfg['engine']) )
{
foreach ( self::$instances as $obj )
{
if ( $obj->signature == $signature )
return $obj->db;
}
try
{ $db = new PDO($cfg['engine'].':host='.$cfg['host'].';dbname='.$cfg['db'], $cfg['user'], $cfg['pass']); }
catch (PDOException $e)
{ self::error($e); }
if ( $db )
{
$t = self::$instances;
array_push($t,$inst);
return $db;
}
}
return false;
}
function __construct($cfg=array())
{
if ( isset($cfg['host'],$cfg['user'],$cfg['pass'],$cfg['db']) )
$cfg['engine'] = isset($cfg['engine']) ? $cfg['engine'] : 'mysql';
else
$cfg = array(
'host' => GEN_DB_HOST,
'user' => GEN_DB_USER,
'pass' => GEN_DB_PASS,
'db' => GEN_DATABASE,
'engine' => GEN_DB_ENGINE
);
if ( isset($cfg['host'],$cfg['user'],$cfg['pass'],$cfg['db'],$cfg['engine']) )
{
if ( $this->db = self::singleton($cfg,$this) )
{
$this->signature = sha1(serialize($cfg));
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if ( $cfg['engine'] == 'mysql' )
{
$this->db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,true);
$this->db->exec('SET CHARACTER SET utf8');
}
}
}
}
public function query($sql)
{
return new genc_query($sql,&$this);
}
}
class genc_query
{
private $sql, $conn, $db, $res, $sequences, $num;
function __construct($sql_statement,$db)
{
$sql_statement = trim($sql_statement);
if ( !empty($sql_statement) )
{
$this->sql = $sql_statement;
$this->conn = &$db;
$this->db = &$db->db;
$this->analyze();
}
}
private function analyze()
{
if ( $this->sql !== null )
{
$this->sequences = genc_db_parser::ParseString($this->sql)->getArray();
}
}
private function execute()
{
if ( $this->res === null )
{
$this->res = false;
if ( isset($this->sequences['select']) )
{
try
{ $this->res = $this->db->query($this->sql); }
catch (Exception $e)
{ genc_database::error($e,$this->sql); }
}
else
{
try
{ $this->res = $this->db->exec($this->sql); }
catch (Exception $e)
{ genc_database::error($e,$this->sql); }
}
}
return $this->res;
}
public function count()
{
if ( $this->num === null )
{
$req = false;
$this->num = false;
if ( isset($this->sequences['select']) )
{
$sql = genc_db_parser::ParseString($this->sql)->getCountQuery();
try
{ $req = $this->db->query($sql); }
catch (Exception $e)
{ genc_database::error($e,$sql); }
if ( $req )
$this->num = $req->fetchColumn();
}
}
return $this->num;
}
public function get_result()
{
if ( $this->execute() )
return $this->res;
return false;
}
public function get_row()
{
$this->execute();
if ( $this->res && isset($this->sequences['select']) )
return $this->res->fetch(PDO::FETCH_ASSOC);
return false;
}
/* Other functions working on the result... */
}
Implementation
/* db is the database object */
$db = new genc_database();
/* concurrent connections can be opened. However giving twice the same argument will return the same corresponding opened connection */
$db2 = new genc_database(array('host'=>'localhost','user'=>'myname','pass'=>'mypass','db'=>'mydb');
/* $db->query($sql) will create a query object ($q) attached to this database */
$q = $db->query(sprintf("
SELECT id,name,modified
FROM users
WHERE id_account = %u",
$id
));
/* $q->count() will return the number of rows returned by the query (through a COUNT), and without taking the limit into account */
echo $q->count();
/* $q->get_row will return the next row of the current recordset indexed by name */
while ( $data = $q->get_row() )
echo $data['id'].': '.$data['name'].'<br />';
/* If we do another action than a select, functions ahead will not return an error but false */
/* On other actions, just to execute the query, use get_result(), which will return the number of affected rows */
$p = $db2->query("UPDATE user2 SET modified = NOW() WHERE id = 1");
echo $p->get_result().'<br />';
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
不好了!
...
什么?
你问了!
好吧,说实话,这并没有那么糟糕,因为它愚蠢。您正在将 PDO 包装在另一个类中。如果您想向 PDO 添加更多功能,您应该扩展它。
PDO 已经在日常运营中做到了这一点。当您
准备
查询时,它返回一个 PDOStatement 对象。您可以将其配置为返回另一个对象(通过PDO::ATTR_STATMENT_CLASS< /code>
),它扩展了 PDOStatement。
如果您想使用解析器预处理查询,则需要覆盖
exec
,query
和prepare
方法位于扩展 PDO 的类中。处理完查询后,您可以调用父方法并返回扩展语句类。如果您担心人们在不经过
exec
/query
/prepare
的情况下调用语句类,请记住,不能执行任何查询执行除非该语句知道如何访问数据库,并且如果没有父 PDO 对象,它将无法执行此操作。而且,
考虑到当时的情况,这是完全荒谬的。您这里有一个 PDO 对象,没有理由不使用 准备好的语句和占位符位于此处。如果您不想一次绑定一个变量(我不怪您),那就是
execute
的可选数组参数 用于。It's bad!
...
What?
You asked!
Okay, in all seriousness, it's not so much bad as it is silly. You're wrapping PDO in another class. If you want to add more functionality to PDO, you should be extending it instead.
PDO already does this during day to day operations. When you
prepare
a query, it returns a PDOStatement object. You can configure it to return another object (viaPDO::ATTR_STATEMENT_CLASS
) that extends PDOStatement instead.If you want to pre-process the query using your parser, you'll need to override the
exec
,query
andprepare
methods in your class that extends PDO. Once you've processed the query, you can call the parent method and return your extended statement class.If you're worried about people invoking the statement class without going through
exec
/query
/prepare
, just keep in mind that no queries can be executed unless the statement knows how to access the database, and it won't be able to do that without the parent PDO object.Also,
This is downright absurd given the circumstances. You have a PDO object here, there's no reason not to use prepared statements and placeholders here. If you don't want to bind one variable at a time (and I don't blame you), that's what
execute
's optional array argument is for.