问题
I'm attempting to design my website based on OOP, but I'm having trouble with how to design the database connection. Currently, I'm creating a private static PDO object in an abstract class, Connector. Obviously, anything that needs to interact with the database will extend this class. I've been flipping back and forth on how to make sure that there is only ever one connection, or PDO object, in a script because some pages will need more than one class that extends Connector. Many people seem to recommend a Singleton pattern for this purpose, but the way I currently do it seems to accomplish the same thing.
Here's my current code.
abstract class Connector
{
private static $dbh;
public function __construct()
{
try
{
self::$dbh = new PDO(...);
self::$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e)
{
die($e->getMessage());
}
}
public function getDB()
{
return self::$dbh;
}
}
Then any subclasses would use it like this.
class Subclass extends Connector
{
public function interactWithDB()
{
$stmt = $this->getDB()->prepare(...);
// etc...
}
}
Theoretically, each instance of a subclass should always be accessing the same PDO instance, I think. Does this code actually make sense and or am I misunderstanding static properties somehow? Is it bad design/practice and or does Singleton have more advantages?
Comment if something's not clear, thanks!
EDIT:
The Connector class wasn't meant to exist just to hold the PDO object. It's destructor closes the connection(makes it null) and it contains functions such isValueTaken, which checks if a value is already in the database. It has the following abstract functions
abstract function retrieveData();
abstract function setData();
For example I have a User class that extends Connector. It's defines setData() to register a user in the database. I don't know if this makes a difference to the response.
回答1:
Obviously, anything that needs to interact with the database will extend this class.
This really makes no sense from an OOP perspective. When some class extends another class that implies an "is a" relationship. If you go this route you are going to have a hard time not violating OCP which is one of the letters in SOLID.
I've been flipping back and forth on how to make sure that there is only ever one connection, or PDO object, in a script because some pages will need more than one class that extends Connector.
Easy! Just create one instance.
Many people seem to recommend a Singleton pattern for this purpose, but the way I currently do it seems to accomplish the same thing.
Many people like that have no clue about OOP principles. Using an singleton just introduces a "fancy" global instance / state.
Does this code actually make sense and or am I misunderstanding static properties somehow?
To be honest it is more a misunderstanding of OOP.
Is it bad design/practice and or does Singleton have more advantages?
See above.
What you should do (in OOP) is inject the database connection into the classes that need it. This makes your code loosely coupled which in turn makes your code better maintainable, testable, debuggable and flexible.
Also I don't really see why you need to create a database class for a pdo connection, because the PDO API itself is already OOP. So unless you have a real reason to write a adapter (can be the case, because there are some) for PDO I would just drop it.
My €0.02
--
In response to your edit:
The Connector class wasn't meant to exist just to hold the PDO object. It's destructor closes the connection(makes it null).
There is often no need at all to close the connection. The connection will automatically be closed once the request is handled (unless we are talking about a persistent connection).
and it contains functions such isValueTaken, which checks if a value is already in the database. It has the following abstract functions
That sounds like a job for another class.
For example I have a User class that extends Connector. It's defines setData() to register a user in the database. I don't know if this makes a difference to the response.
No my point still stands. There is no need for a user to inherit from a database. Doesn't that sounds strange. A user inheriting from a database (I don't want to meet that person). You should inject the database connection into the User if it is needed.
回答2:
Preface
The singleton approach is generally frowned upon. You will be set upon by raptors.
What you're essentially asking is how to configure a connection and make it globally available. This is generally referred to as global state. You can achieve this using a container class and static methods.
Here's an example
namespace Persistence\Connection\Config;
interface PDOConfig {
public function getDSN();
public function getUsername();
public function getPassword();
public function getDriverOptions();
}
class MySqlConfig implements PDOConfig {
private $username;
private $password;
private $db;
private $host = 'localhost';
private $charset = 'utf8';
public function __construct($username, $password, $db) {
$this->username = $username;
$this->password = $password;
$this->db = $db;
}
// getters and setters, etc
public function getDSN() {
return sprintf('mysql:host=%s;dbname=%s;charset=%s',
$this->host, $this->db, $this->charset);
}
public function getDriverOptions() {
return [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
];
}
}
namespace Persistence\Connection;
use Persistence\Connection\Config\PDOConfig;
class Registry {
private static $connection;
private static $config;
public static function setConfig(PDOConfig $config) {
self::$config = $config;
}
public static getConnection() {
if (self::$connection === null) {
if (self::$config === null) {
throw new RuntimeException('No config set, cannot create connection');
}
$config = self::$config;
self::$connection = new \PDO($config->getDSN(), $config->getUsername(),
$config->getPassword(), $config->getDriverOptions());
}
return self::$connection;
}
}
Then, at some point (early) in your application execution cycle
use Persistence\Connection\Config\MySqlConfig;
use Persistence\Connection\Registry;
$config = new MySqlConfig('username', 'password', 'dbname');
Registry::setConfig($config);
Then, you can use
Registry::getConnection();
anywhere in your code to retrieve the PDO instance.
回答3:
Just a side note if anyone is reading this, if anybody is using the above code snippet by Phil, remember to use a black slash infront of PDO inside getDriverOptions() so as to refer to the global namespace. it should look like this.
public function getDriverOptions() {
return [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_EMULATE_PREPARES => false,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC
];
}
来源:https://stackoverflow.com/questions/19848384/php-pdo-instance-as-private-static-property