PDO (PHP Data Objects). Capa de Abstracción de acceso a Bases de Datos utilizando el Patrón de diseño Singleton (Segunda parte)
Nuestra clase Database que usa internamente el Patrón Singleton: crea una instancia de la clase PDO a través del método getInstance(), pudiendo así invocar todos los métodos y propiedades de la misma.
¿Pero que inconveniente tendríamos si quisiéramos cambiar la Capa de Abstracción de acceso a la Bases de Datos para utilizar ADODB en vez de PDO?
Supongamos que desarrollamos todo nuestro sistema invocando las propiedades y métodos que nos provee PDO.
Inconvenientes
Estaríamos atados a una implementación concreta, con lo que rompemos con una premisa del Diseño Orientado a Objetos.
“No dependas de implementaciones concretas, solo de implementaciones abstractas”
Tendríamos que re implementar toda nuestra lógica de negocio donde quiera que hayamos utilizado una propiedad o método pertenecientes a PDO.
Solución
Adaptar la clase Database para que se abstraiga de las propiedades y métodos concretos que proveen las herramientas de Abstracción de Acceso a Bases de Datos como PDO y ADODB.
Nuestra clase Database debe contener todas las operaciones necesarias para interactuar con cualquier base de datos, pero estos métodos no estarán programados desde cero (reutilización de código), sino que reutilizan internamente algún servicio (extensión o librería) de abstracción de BD. En este caso utilizaremos PDO.
Ejemplo
database.php
- <?php
- require_once 'web.config.php';
- /**
- * Represents a connection between PHP and a database server
- *
- */
- final class Database extends PDO
- {
- static private $dns = DNS;
- static private $username = USERNAME;
- static private $passwd = PASSWD;
- static private $options;
- static private $instance;
- static private $constructIsPrivate = true;
- /**
- * A private constructor; prevents direct creation of object
- *
- * @access static private
- */
- public function __construct()
- {
- if (self::$constructIsPrivate) {
- trigger_error('Call to private ' . __CLASS__ .
- '::__construct() from invalid context', E_USER_ERROR);
- }
- try {
- parent::__construct(self::$dns, self::$username, self::$passwd);
- $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- } catch (PDOException $e) {
- echo 'Connection failed: ' . $e->getMessage();
- }
- }
- /**
- * Create a instance of Database class with The singleton method
- *
- * @access static public
- * @return Database object
- */
- static public function getInstance()
- {
- if (!isset(self::$instance))
- {
- self::$constructIsPrivate = false;
- $c = __CLASS__;
- self::$instance = new $c;
- self::$constructIsPrivate = true;
- }
- return self::$instance;
- }
- /**
- * Initiates a transaction
- *
- * @access public
- * @return bool
- */
- public function beginTransaction()
- {
- return parent::beginTransaction();
- }
- /**
- * Commits a transaction
- *
- * @access public
- * @return bool
- */
- public function commit()
- {
- return parent::commit();
- }
- /**
- * Fetch the SQLSTATE associated with the last operation on the database handle
- *
- * @access public
- * @return string
- */
- public function errorCode()
- {
- return parent::errorCode();
- }
- /**
- * Fetch extended error information associated with the last operation on the database handle
- *
- * @access public
- * @return array
- */
- public function errorInfo()
- {
- return parent::errorInfo();
- }
- /**
- * Execute an SQL statement and return the number of affected rows
- *
- * @access public
- * @param string $statement
- * @return int
- */
- public function exec($statement)
- {
- return parent::exec($statement);
- }
- /**
- * Return an array of available PDO drivers
- *
- * @access static public
- * @return array
- */
- static public function getAvailableDrivers()
- {
- return parent::getAvailableDrivers();
- }
- /**
- * Returns the ID of the last inserted row or sequence value
- *
- * @access public
- * @param string[optional] $name
- * @return string
- */
- public function lastInsertId($name = null)
- {
- return parent::lastInsertId($name);
- }
- /**
- * Prepares a statement for execution and returns a statement object
- *
- * @access public
- * @param string $statement
- * @param array $driver_options
- * @return PDOStatement
- */
- public function prepare($statement, $driver_options = array() )
- {
- return parent::prepare($statement, $driver_options);
- }
- /**
- * Executes an SQL statement, returning a result set as a PDOStatement object
- *
- * @access public
- * @param string $statement
- * @return PDOStatement
- */
- public function query()
- {
- $args = func_get_args();
- switch(func_num_args()) {
- case 1:
- return parent::query($args[0]);
- break;
- case 2:
- return parent::query($args[0], $args[1]);
- break;
- case 3:
- return parent::query($args[0], $args[1], $args[2]);
- break;
- case 4:
- return parent::query($args[0], $args[1], $args[2], $args[3]);
- break;
- }
- }
- /**
- * Quotes a string for use in a query
- *
- * @access public
- * @param string $string
- * @param int[optional] $parameter_type
- * @return string
- */
- public function quote($string, $parameter_type = PDO::PARAM_STR )
- {
- return parent::quote($string, $parameter_type);
- }
- /**
- * Rolls back a transaction
- *
- * @access public
- * @return bool
- */
- public function rollBack()
- {
- return parent::rollBack();
- }
- /**
- * Set an attribute
- *
- * @access public
- * @param int $attribute
- * @param mixed $value
- * @return bool
- */
- public function setAttribute($attribute, $value)
- {
- return parent::setAttribute($attribute, $value);
- }
- /**
- * Bind a column to a PHP variable
- *
- * @access public
- * @param mixed $column
- * @param mixed $param
- * @param int[optional] $type
- * @param int[optional] $maxlen
- * @param mixed[optional] $driverdata
- * @return bool
- */
- public function bindColumn($column, &$param, $type = null, $maxlen = null, $driverdata = null)
- {
- return parent::bindColumn($column, &$param, $type, $maxlen, $driverdata);
- }
- /**
- * Binds a parameter to the specified variable name
- *
- * @access public
- * @param mixed $parameter
- * @param mixed $variable
- * @param int[optional] $data_type
- * @param int[optional] $length
- * @param mixed[optional] $driver_options
- * @return bool
- */
- public function bindParam($parameter, &$variable, $data_type = PDO::PARAM_STR, $length = null, $driver_options = null)
- {
- return parent::bindParam($parameter, &$variable, $data_type, $length, $driver_options);
- }
- /**
- * Binds a value to a parameter
- *
- * @access public
- * @param mixed $parameter
- * @param mixed $value
- * @param int[optional] $data_type
- * @return bool
- */
- public function bindValue($parameter, $value, $data_type = PDO::PARAM_STR)
- {
- return parent::bindValue($parameter, $value, $data_type);
- }
- /**
- * Closes the cursor, enabling the statement to be executed again
- *
- * @access public
- * @return bool
- */
- public function closeCursor()
- {
- return parent::closeCursor();
- }
- /**
- * Returns the number of columns in the result set
- *
- * @access public
- * @return int
- */
- public function columnCount()
- {
- return parent::columnCount();
- }
- /**
- * Dump a SQL prepared command
- *
- * @access public
- */
- public function debugDumpParams()
- {
- return parent::debugDumpParams();
- }
- /**
- * Executes a prepared statement
- *
- * @access public
- * @param array $input_parameters
- * @return bool
- */
- public function execute($input_parameters = array())
- {
- return parent::execute($input_parameters);
- }
- /**
- * Fetches the next row from a result set
- *
- * @access public
- * @param int[optional] $fetch_style
- * @param int[optional] $cursor_orientation
- * @param int[optional] $cursor_offset
- * @return mixed
- */
- public function fetch($fetch_style = PDO::FETCH_BOTH, $cursor_orientation = PDO::FETCH_ORI_NEXT, $cursor_offset = 0)
- {
- return parent::fetch($fetch_style, $cursor_orientation, $cursor_offset);
- }
- /**
- * Returns an array containing all of the result set rows
- *
- * @access public
- * @param int[optional] $fetch_style
- * @param int[optional] $column_index
- * @param array $ctor_args
- * @return array
- */
- public function fetchAll($fetch_style = PDO::FETCH_BOTH, $column_index = 0 , $ctor_args = array())
- {
- return parent::fetchAll($fetch_style, $column_index, $ctor_args);
- }
- /**
- * Returns a single column from the next row of a result set
- *
- * @access public
- * @param int[optional] $column_number
- * @return string
- */
- public function fetchColumn($column_number = 0)
- {
- return parent::fetchColumn($column_number);
- }
- /**
- * Fetches the next row and returns it as an object
- *
- * @access public
- * @param string[optional] $class_name
- * @param array $ctor_args
- * @return mixed
- */
- public function fetchObject($class_name = 'stdClass', $ctor_args = null)
- {
- return parent::fetchObject($class_name, $ctor_args);
- }
- /**
- * Retrieve a attribute
- *
- * @access public
- * @param int $attribute
- * @return mixed
- */
- public function getAttribute($attribute)
- {
- return parent::getAttribute($attribute);
- }
- /**
- * Returns metadata for a column in a result set
- *
- * @access public
- * @param int $column
- * @return array
- */
- public function getColumnMeta($column)
- {
- return parent::getColumnMeta($column);
- }
- /**
- * Advances to the next rowset in a multi-rowset statement handle
- *
- * @access public
- * @return bool
- */
- public function nextRowset()
- {
- return parent::nextRowset();
- }
- /**
- * Returns the number of rows affected by the last SQL statement
- *
- * @access public
- * @return int the number of rows.
- */
- public function rowCount()
- {
- return parent::rowCount();
- }
- /**
- * Set the default fetch mode for this statement
- *
- * @access public
- * @param $PDO
- * @param object $object
- * @return bool
- */
- public function setFetchMode()
- {
- $args = func_get_args();
- switch(func_num_args()) {
- case 1:
- return parent::setFetchMode($args[0]);
- break;
- case 2:
- return parent::setFetchMode($args[0], $args[1]);
- break;
- case 3:
- return parent::setFetchMode($args[0], $args[1], $args[2]);
- break;
- }
- }
- /**
- * Prevent users to clone the instance
- *
- * @access public
- * @return string trigger_error
- */
- public function __clone()
- {
- trigger_error('Clone is not allowed.', E_USER_ERROR);
- }
- }
- ?>
Ventaja
Si en un futuro, encontramos otra capa de abstracción que se adapte más a nuestro contexto (rendimiento, flexibilidad, simplicidad, etc), podremos reemplazarla, sin que la lógica de nuestra aplicación se vea afectada por el cambio, (nuestra lógica depende de nuestra clase Database, y no concretamente de una capa de abstracción específica).
Artículos Relacionados
- Patrón "Singleton" en PHP5
- PDO (PHP Data Objects). Capa de Abstracción de acceso a Bases de Datos (Primera parte)
- PDO (PHP Data Objects). Capa de Abstracción de acceso a Bases de Datos utilizando el Patrón de diseño Singleton (Segunda parte)
Bibliografía
Enrique Place, (25 de mayo del 2006). «Comentarios sobre: "¿Cual es la mejor capa de abstracción?"». Consultado el 1 de diciembre de 2009.