Abstraerse de los servicios que proveen las herramientas de abstracción de acceso a Bases de Datos

Como habíamos visto en nuestro post anterior

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

  1. <?php  
  2. require_once 'web.config.php';  
  3.   
  4. /** 
  5.  * Represents a connection between PHP and a database server 
  6.  * 
  7.  */  
  8. final class Database extends PDO  
  9. {  
  10.     static private  $dns = DNS;  
  11.     static private  $username = USERNAME;  
  12.     static private  $passwd = PASSWD;  
  13.     static private  $options;  
  14.     static private  $instance;  
  15.     static private  $constructIsPrivate = true;  
  16.   
  17.     /** 
  18.      * A private constructor; prevents direct creation of object 
  19.      * 
  20.      * @access static private 
  21.      */  
  22.     public function __construct()  
  23.     {  
  24.       if (self::$constructIsPrivate) {  
  25.          trigger_error('Call to private ' . __CLASS__ .   
  26.                        '::__construct() from invalid context', E_USER_ERROR);  
  27.       }  
  28.       try {  
  29.            parent::__construct(self::$dns, self::$username, self::$passwd);  
  30.            $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  
  31.       } catch (PDOException $e) {  
  32.            echo 'Connection failed: ' . $e->getMessage();  
  33.       }  
  34.     }  
  35.   
  36.     /** 
  37.      * Create a instance of Database class with The singleton method 
  38.      * 
  39.      * @access static public 
  40.      * @return Database object 
  41.      */  
  42.      static public function getInstance()  
  43.      {  
  44.        if (!isset(self::$instance))  
  45.        {  
  46.          self::$constructIsPrivate = false;  
  47.          $c = __CLASS__;  
  48.          self::$instance = new $c;  
  49.          self::$constructIsPrivate = true;  
  50.        }  
  51.         return self::$instance;  
  52.      }  
  53.   
  54.     /** 
  55.      * Initiates a transaction 
  56.      * 
  57.      * @access public 
  58.      * @return bool 
  59.      */  
  60.      public function beginTransaction()  
  61.      {  
  62.         return parent::beginTransaction();  
  63.      }  
  64.   
  65.     /** 
  66.      * Commits a transaction  
  67.      * 
  68.      * @access public 
  69.      * @return bool 
  70.      */  
  71.      public function commit()  
  72.      {  
  73.     return parent::commit();  
  74.      }  
  75.   
  76.     /** 
  77.      * Fetch the SQLSTATE associated with the last operation on the database handle 
  78.      * 
  79.      * @access public 
  80.      * @return string 
  81.      */  
  82.      public function errorCode()  
  83.      {  
  84.     return parent::errorCode();  
  85.      }  
  86.   
  87.     /** 
  88.      * Fetch extended error information associated with the last operation on the database handle 
  89.      * 
  90.      * @access public 
  91.      * @return array 
  92.      */  
  93.      public function errorInfo()  
  94.      {  
  95.     return parent::errorInfo();  
  96.      }  
  97.   
  98.     /** 
  99.      * Execute an SQL statement and return the number of affected rows 
  100.      * 
  101.      * @access public 
  102.      * @param  string $statement 
  103.      * @return int 
  104.      */  
  105.      public function exec($statement)  
  106.      {  
  107.     return parent::exec($statement);  
  108.      }  
  109.   
  110.     /** 
  111.      * Return an array of available PDO drivers 
  112.      * 
  113.      * @access static public 
  114.      * @return array 
  115.      */  
  116.      static public function getAvailableDrivers()  
  117.      {  
  118.     return parent::getAvailableDrivers();  
  119.      }  
  120.   
  121.     /** 
  122.      * Returns the ID of the last inserted row or sequence value  
  123.      * 
  124.      * @access public 
  125.      * @param  string[optional] $name 
  126.      * @return string 
  127.      */  
  128.      public function lastInsertId($name = null)  
  129.      {  
  130.     return parent::lastInsertId($name);  
  131.      }  
  132.   
  133.     /** 
  134.      * Prepares a statement for execution and returns a statement object 
  135.      * 
  136.      * @access public 
  137.      * @param  string $statement 
  138.      * @param  array  $driver_options 
  139.      * @return PDOStatement 
  140.      */  
  141.      public function prepare($statement$driver_options = array() )  
  142.      {  
  143.     return parent::prepare($statement$driver_options);  
  144.      }  
  145.   
  146.     /** 
  147.      * Executes an SQL statement, returning a result set as a PDOStatement object 
  148.      * 
  149.      * @access public 
  150.      * @param  string $statement 
  151.      * @return PDOStatement 
  152.      */  
  153.      public function query()  
  154.      {  
  155.         $args = func_get_args();  
  156.         switch(func_num_args()) {  
  157.             case 1:  
  158.                 return parent::query($args[0]);  
  159.                 break;  
  160.             case 2:  
  161.                 return parent::query($args[0], $args[1]);  
  162.                 break;  
  163.             case 3:  
  164.                 return parent::query($args[0], $args[1], $args[2]);  
  165.                 break;  
  166.             case 4:  
  167.                 return parent::query($args[0], $args[1], $args[2], $args[3]);  
  168.                 break;  
  169.         }  
  170.       }  
  171.   
  172.     /** 
  173.      * Quotes a string for use in a query 
  174.      * 
  175.      * @access public 
  176.      * @param  string        $string 
  177.      * @param  int[optional] $parameter_type 
  178.      * @return string 
  179.      */  
  180.      public function quote($string$parameter_type = PDO::PARAM_STR )  
  181.      {  
  182.     return parent::quote($string$parameter_type);  
  183.      }  
  184.   
  185.     /** 
  186.      * Rolls back a transaction  
  187.      * 
  188.      * @access public 
  189.      * @return bool 
  190.      */  
  191.      public function rollBack()  
  192.      {  
  193.     return parent::rollBack();  
  194.      }  
  195.   
  196.     /** 
  197.      * Set an attribute 
  198.      * 
  199.      * @access public 
  200.      * @param  int   $attribute 
  201.      * @param  mixed $value 
  202.      * @return bool 
  203.      */  
  204.      public function setAttribute($attribute$value)  
  205.      {  
  206.     return parent::setAttribute($attribute$value);  
  207.      }  
  208.   
  209.     /** 
  210.      * Bind a column to a PHP variable  
  211.      * 
  212.      * @access public 
  213.      * @param  mixed           $column 
  214.      * @param  mixed           $param 
  215.      * @param  int[optional]   $type 
  216.      * @param  int[optional]   $maxlen 
  217.      * @param  mixed[optional] $driverdata 
  218.      * @return bool 
  219.      */  
  220.      public function bindColumn($column, &$param$type = null, $maxlen = null, $driverdata = null)  
  221.      {  
  222.     return parent::bindColumn($column, &$param$type$maxlen$driverdata);  
  223.      }  
  224.   
  225.     /** 
  226.      * Binds a parameter to the specified variable name 
  227.      * 
  228.      * @access public 
  229.      * @param  mixed           $parameter  
  230.      * @param  mixed           $variable  
  231.      * @param  int[optional]   $data_type 
  232.      * @param  int[optional]   $length 
  233.      * @param  mixed[optional] $driver_options 
  234.      * @return bool 
  235.      */  
  236.      public function bindParam($parameter, &$variable$data_type = PDO::PARAM_STR, $length = null, $driver_options = null)  
  237.      {  
  238.     return parent::bindParam($parameter, &$variable$data_type$length$driver_options);  
  239.      }  
  240.   
  241.     /** 
  242.      * Binds a value to a parameter 
  243.      * 
  244.      * @access public 
  245.      * @param  mixed         $parameter 
  246.      * @param  mixed         $value 
  247.      * @param  int[optional] $data_type 
  248.      * @return bool 
  249.      */  
  250.      public function bindValue($parameter$value$data_type = PDO::PARAM_STR)  
  251.      {  
  252.     return parent::bindValue($parameter$value$data_type);  
  253.      }  
  254.   
  255.     /** 
  256.      * Closes the cursor, enabling the statement to be executed again 
  257.      * 
  258.      * @access public 
  259.      * @return bool 
  260.      */  
  261.      public function closeCursor()  
  262.      {  
  263.     return parent::closeCursor();  
  264.      }  
  265.   
  266.     /** 
  267.      * Returns the number of columns in the result set 
  268.      * 
  269.      * @access public 
  270.      * @return int 
  271.      */  
  272.      public function columnCount()  
  273.      {  
  274.     return parent::columnCount();  
  275.      }  
  276.   
  277.     /** 
  278.      * Dump a SQL prepared command 
  279.      * 
  280.      * @access public 
  281.      */  
  282.      public function debugDumpParams()  
  283.      {  
  284.     return parent::debugDumpParams();  
  285.      }  
  286.   
  287.     /** 
  288.      * Executes a prepared statement  
  289.      * 
  290.      * @access public 
  291.      * @param  array $input_parameters 
  292.      * @return bool 
  293.      */  
  294.      public function execute($input_parameters = array())  
  295.      {  
  296.     return parent::execute($input_parameters);  
  297.      }  
  298.   
  299.     /** 
  300.      * Fetches the next row from a result set 
  301.      * 
  302.      * @access public 
  303.      * @param  int[optional] $fetch_style 
  304.      * @param  int[optional] $cursor_orientation 
  305.      * @param  int[optional] $cursor_offset 
  306.      * @return mixed 
  307.      */  
  308.      public function fetch($fetch_style = PDO::FETCH_BOTH, $cursor_orientation = PDO::FETCH_ORI_NEXT, $cursor_offset = 0)  
  309.      {  
  310.     return parent::fetch($fetch_style$cursor_orientation$cursor_offset);  
  311.      }  
  312.   
  313.     /** 
  314.      * Returns an array containing all of the result set rows 
  315.      * 
  316.      * @access public 
  317.      * @param  int[optional] $fetch_style 
  318.      * @param  int[optional] $column_index 
  319.      * @param  array         $ctor_args  
  320.      * @return array 
  321.      */  
  322.      public function fetchAll($fetch_style = PDO::FETCH_BOTH, $column_index = 0 , $ctor_args = array())  
  323.      {  
  324.     return parent::fetchAll($fetch_style$column_index$ctor_args);  
  325.      }  
  326.   
  327.     /** 
  328.      * Returns a single column from the next row of a result set 
  329.      * 
  330.      * @access public 
  331.      * @param  int[optional] $column_number 
  332.      * @return string 
  333.      */  
  334.      public function fetchColumn($column_number = 0)  
  335.      {  
  336.     return parent::fetchColumn($column_number);  
  337.      }  
  338.   
  339.     /** 
  340.      * Fetches the next row and returns it as an object 
  341.      * 
  342.      * @access public 
  343.      * @param  string[optional] $class_name 
  344.      * @param  array            $ctor_args 
  345.      * @return mixed 
  346.      */  
  347.      public function fetchObject($class_name = 'stdClass'$ctor_args = null)  
  348.      {  
  349.     return parent::fetchObject($class_name$ctor_args);  
  350.      }  
  351.   
  352.     /** 
  353.      * Retrieve a attribute 
  354.      * 
  355.      * @access public 
  356.      * @param  int $attribute 
  357.      * @return mixed 
  358.      */  
  359.      public function getAttribute($attribute)  
  360.      {  
  361.     return parent::getAttribute($attribute);  
  362.      }  
  363.   
  364.     /** 
  365.      * Returns metadata for a column in a result set 
  366.      * 
  367.      * @access public 
  368.      * @param  int $column 
  369.      * @return array 
  370.      */  
  371.      public function getColumnMeta($column)  
  372.      {  
  373.     return parent::getColumnMeta($column);  
  374.      }  
  375.   
  376.     /** 
  377.      * Advances to the next rowset in a multi-rowset statement handle 
  378.      * 
  379.      * @access public 
  380.      * @return bool 
  381.      */  
  382.      public function nextRowset()  
  383.      {  
  384.     return parent::nextRowset();  
  385.      }  
  386.   
  387.     /** 
  388.      * Returns the number of rows affected by the last SQL statement 
  389.      * 
  390.      * @access public 
  391.      * @return int the number of rows.  
  392.      */  
  393.      public function rowCount()  
  394.      {  
  395.     return parent::rowCount();  
  396.      }  
  397.   
  398.     /** 
  399.      * Set the default fetch mode for this statement 
  400.      * 
  401.      * @access public 
  402.      * @param  $PDO 
  403.      * @param  object $object  
  404.      * @return bool  
  405.      */  
  406.      public function setFetchMode()  
  407.      {  
  408.         $args = func_get_args();  
  409.         switch(func_num_args()) {  
  410.             case 1:  
  411.                 return parent::setFetchMode($args[0]);  
  412.                 break;  
  413.             case 2:  
  414.                 return parent::setFetchMode($args[0], $args[1]);  
  415.                 break;  
  416.             case 3:  
  417.                 return parent::setFetchMode($args[0], $args[1], $args[2]);  
  418.                 break;  
  419.         }  
  420.      }  
  421.   
  422.     /** 
  423.      * Prevent users to clone the instance 
  424.      * 
  425.      * @access public 
  426.      * @return string trigger_error 
  427.      */  
  428.     public function __clone()  
  429.     {  
  430.         trigger_error('Clone is not allowed.', E_USER_ERROR);  
  431.     }  
  432. }  
  433. ?>  

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

3 comentarios:

  1. Gran trabajo. El post es de hace unos meses, pero espero me puedas responder a una cuestion:
    ¿Seria conveniente a su vez (o modificando esta) una clase con métodos estilo 'insertar(tabla,valores, campos,...)' o 'seleccionar(tabla, condicion,...)?
    Gracias, un saludo

    ResponderEliminar
  2. ¿cómo le pasarías parámetros tipo
    array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")?

    ResponderEliminar
  3. muy buen articulo, podrias por favor poner un ejemplo test.php para ver utilisar las funciones con un select. Gracias

    ResponderEliminar