Pregunta:
¿Es dinámica esta estructura de envoltura de DOP? ¿Puede el uso de self::
lugar de $this
causar problemas?
class Database
extends PDO
{
/*@var mixed $stmt Used as a temporary variable to hold queries*/
private $stmt = Null;
/*@var $scopeSelectVar Used as a temporary variable to hold queries*/
private $scopeSelectVar = Null;
/*@Constructor __construct
*
* Database __construct ( mixed $db, [ string $serverHost = Null], [ string $dbName = Null],
* [ string $dbUser = Null], [ string $password = Null], [ bool $persistent = Null])
*
* @access-public
*/
public function __construct($db,
$serverHost = Null,
$dbName = Null, $dbUser = Null,
$password = Null, $persistent = true)
{
try
{
if(is_array($db))
{
/*
* parent::_construct allows easy instantiation and multiple database connections.
* @param mixed $db When array(), var holds all parameters required to connect.
* When string, var holds the type of DB to connect to. E.g. mysql
*
* @param string $serverHost the host of the DB connection
* @param string $dbName the name of the database
* @param string $dbUser the respective name of the user
* @param string $password the password of the user connecting
* @param bool $persistent if true, allows connection to persist through the app
*
*/
parent::__construct("{$db['dbType']}:host={$db['Host']};
dbname={$db['dbName']}",
$db['dbUser'],
$db['dbPassKey'],
array(
PDO::ATTR_PERSISTENT => $persistent,
));
}//if Var $db == array() then connect using array parameters
else
{
//else connect using all the other function parameters
parent::__construct("{$db}:host={$serverHost};
dbname={$dbName}", $dbUser, $password,
array(
PDO::ATTR_PERSISTENT => $persistent,
));
}
/*Uncomment if you have a proper log class*/
Log::write('../AppLogs/databaseConn.log', 'Connection
Established at:');
}
catch(PDOException $e)/*catching pdo exception and writing it to a file*/
{
/*Uncomment if you have a proper log class*/
Log::write('../AppLogs/databaseErrors.log',
'Error:'
.$e->getMessage());
}
}
/*
* @access private
*
* void defineParamType(string $val)
*
* */
private function defineParamType($val)
{
/*@param string $val the update or insert query data passed*/
/*@return const $param the param type constant returned from the function*/
switch($val):
case (is_int($val)):
$param = PDO::PARAM_INT;
break;
case (is_string($val)):
$param = PDO::PARAM_STR;
break;
case (is_bool($val)):
$param = PDO::PARAM_BOOL;
break;
case (is_Null($val)):
$param = PDO::PARAM_Null;
break;
default:
$param = Null;
endswitch;
return $param;
}
/*
* @access private
*
* void insertPrepare(array $bindData)
* */
private function insertPrepare($bindData)
{
/* @param array $bindData Data to be prepared for binding
* @return array $insertArray
* */
ksort($bindData);
$insertArray = array(
'fields' => implode("`,`", array_keys($bindData)),
'placeholders' => ':'.implode(',:',array_keys($bindData)),
);
return $insertArray;
}
/*
* @access private
*
* void updatePrepare(array $bindData)
* */
private function updatePrepare($bindData)
{
/*
* @param array $bindData Data to be prepared for binding
* @return string $placeHolders
* */
ksort($bindData);
$placeHolders = Null;
foreach($bindData as $key => $val)
{
$placeHolders .= "`$key`=:$key, ";
}
$placeHolders = rtrim($placeHolders, ', ');
return $placeHolders;
}
private function where($arguments = array(), $joinKeyword = 'AND')
{
ksort($arguments);
$whereClause = Null;
foreach($arguments as $key => $val)
{
if(is_int($val))
{
$whereClause .= "`$key` = $val {$joinKeyword} ";
}
else
{
$whereClause .= "`$key` = '$val' {$joinKeyword} ";
}
}
$whereClause = rtrim($whereClause, ' '.$joinKeyword.' ');
$whereClause = "WHERE {$whereClause}";
return $whereClause;
}
/*
* @access public
*
* void query(string $sql)
* */
public function query($sql)
{
/*
* @param string $sql the sql command to execute
* */
$this->scopeSelectVar = NULL;
$this->stmt = parent::query($sql);
}
/*
* @access public
*
* void insertRow(string $tableName, string $bindData)
*
* $array = array('field1'=>'field1Value')<-Notice the abscence of ":"
* $handler->insertRow('table', $array)
*
* */
public function insertRow($tableName, $bindData)
{
/*
* @param string $tableName Name of the table that is inserted into
* @param array $bindData array holding the set of column names
* respective data to be inserted
* */
try
{
$insertData = self::insertPrepare($bindData);
$this->stmt = parent::prepare("INSERT INTO
`{$tableName}` (`{$insertData['fields']}`)
VALUES({$insertData['placeholders']})");
foreach($bindData as $key => $val)
{
$param = self::defineParamType($val);
$this->stmt->bindValue(":$key", $val, $param);
}
$query = $this->stmt->execute();
}
catch(PDOException $e)
{
}
}
/*
* @access public
*
* void updateRow(string $tableName, array $bindData, string $target)
*
* Way of use: to update
* $array = array('field1'=>'field1Value')<-Notice the abscence of ":"
* $handler->updateRow('table', $array, '`field2`='Val'')
* */
public function updateRow($tableName, $bindData, $target,
$targetClause = 'AND')
{
/*
* @param string $tableName The name of the table you're updating
* @param array $bindData array of the values to be inserted.
* includes $_POST and $_GET
* @param string $target The exact update target. I.e.
* WHERE id='?'
* */
try
{
$updateData = self::updatePrepare($bindData);
if(isset($target))
{
$target = self::where($target, $targetClause);
}
$this->stmt = parent::prepare("UPDATE {$tableName}
SET {$updateData} {$target}");
foreach($bindData as $key => $val)
{
$param = self::defineParamType($val);
$this->stmt->bindValue(":$key", $val, $param);
}
$this->stmt->execute();
}
catch(PDOException $e)
{
}
}
/*
* @access public
*
* void deleteRow(string $tableName, string $target)
* */
public function deleteRow($tableName, $target)
{
/*
* @param string $tableName table to be deleted from
* @param string $target target of the delete query
* */
try
{
return parent::exec("DELETE FROM {$tableName} WHERE
{$target}");
}
catch(PDOException $e)
{
}
}
/*
* @access public
*
* void selectRow(string $fields, string $tableName, string $target)
* */
public function selectRow($fields, $tableName, array $target = NULL
, $targetClause = 'AND')
{
/*
* @param string $fields the fields of selection. E.g. '`field`,`field2`'...
* @param string $tableName The name of the target table
* */
if(isset($target))
{
$where = self::where($target, $targetClause);
}else
{
$where = Null;
}
self::query("SELECT {$fields} FROM {$tableName} {$where}");
}
/*
* @access public
*
* void fetch([string $singleReturn = false], [constant $fetchMode = PDO::FETCH_OBJ])
* */
public function fetch($singleReturn = false,
$fetchMode = PDO::FETCH_OBJ)
{
/*
* @param string $singleReturn the name of the "single" field to be fetching
* @param constant $fetchMode The fetch mode in which the data recieved will be stored
* @return mixed Null when conditions are not met, stdClass(object) or string when
* conditions are met.
* */
if(!isset($this->stmt)){return false;}
if($singleReturn == true)
{
if($this->scopeSelectVar == false)
{
$this->scopeSelectVar = $this->stmt->fetch($fetchMode);
if(isset($this->scopeSelectVar->$singleReturn))
{
return $this->scopeSelectVar->$singleReturn;
}else
return false;
}
}else
{
$this->scopeSelectVar = $this->stmt->fetch($fetchMode);
return (isset($this->scopeSelectVar)) ?
$this->scopeSelectVar: new StdClass;
}
}
/*
* @access public
*
* void fetchAll([constant $fetchMode = PDO::FETCH_ASSOC])
* */
public function fetchAll($fetchMode = PDO::FETCH_ASSOC)
{
/*
* @param constant $fetchMode Default is PDO::FETCH_ASSOC the mode of fetching data
* */
if(!isset($this->stmt)){return false;}
$fetchVar = $this->stmt->fetchAll($fetchMode);
return (!empty($fetchVar)) ? $fetchVar: new StdClass;
}
/*
* @TODO set a convenient method to quicly setup nested queries
* */
public function setSubQuery($target, $subQuery,
$mysqlSubQueryHandler)
{
//mysql nested query handler
}
/*
* @access public
*
* void rowCount()
* */
public function rowCount()
{
/*
* @return numeric $this->stmt->rowCount()
* */
if(isset($this->stmt))
{
return $this->stmt->rowCount();
}
}
/*
* @access public
*
* void lastId()
*
* */
public function lastId()
{
if(isset($this->stmt))
{
return parent::lastInsertId();
}
}
/*
* @access public
*
* void truncateTable(string $tableName)
* */
public function truncateTable($tableName)
{
/*
* @param string $tableName The name of table to be truncated
* Notice: truncation will reset the table and delete the data
* */
return self::query("TRUNCATE TABLE {$tableName}");
echo "Table {$tableName} Truncated on:".date("d-m-Y h:i:s")
."\n";
}
/*
* @access public
* void debugDumpParams()
*
* */
public function debugDumpParams()
{
return $this->stmt->debugDumpParams();
}
public function rankTable($tableName, $rankableColumn,
$orderByColumn, $ascDesc='DESC', $sqlVarName = 'rankNum')
{
self::query("SET @{$sqlVarName}:= 0");
self::query("UPDATE `{$tableName}` SET {$rankableColumn}
=@{$sqlVarName}:=@{$sqlVarName}+1
ORDER BY `{$orderByColumn}` {$ascDesc}");
}
public function
public function __destruct()
{
}
}
Sé que probablemente no debería extenderme a PDO, pero se rediseñará toda la arquitectura. Solo necesito asegurarme de que las otras funciones estén bien codificadas.
Este es mi primer diseño de código OOP pesado. Por favor critique.
Respuesta:
Cuando se trata de hacer cosas como esta, es sorprendente lo rápido que se puede complicar y la cantidad de ideas y principios de programación orientada a objetos que pueden estar involucrados.
De todos modos, hay mucho que cubrir aquí, así que solo voy a resumir algunas cosas y puedo volver y editar más detalles más adelante.
Para un "primer diseño de código OOP pesado", esto es bastante bueno (probablemente mejor que el típico primer intento real), pero hay algunas cosas que se destacaron:
-
self::
es casi siempre una mala señal- Significa que una variable es básicamente un espacio de nombres global
- Significa que un método no tiene absolutamente nada que ver con el estado de un objeto de la clase a la que pertenece. Esto puede tener sentido en algunas situaciones, pero es una decisión que debe considerarse cuidadosamente a menos que sea uno de los usos obvios.
- Su código es incorrecto desde un punto de vista técnico porque no puede, bueno, no debería, ya que PHP es demasiado indulgente, llamar a un método no estático de forma estática. No tiene sentido
$this
yself
no son intercambiables en absoluto.
- Tu clase es específica de MySQL
- Las comillas inversas son específicas de MySQL
- Algunas otras cosas que soy demasiado vago para mirar hacia atrás a través del código y volver a encontrar 🙂
- Está mezclando preocupaciones con la creación de consultas y la conexión a la base de datos.
-
prepare
,query
,query
operaciones de nivel bastante bajo - crear declaraciones con métodos como
where
es una operación de nivel bastante alto- Si desea un DBAL, cree un DBAL. Un DBAL debe tener una conexión, no una conexión.
- La gente, por alguna razón, siempre quiere extender la DOP. Hay situaciones en las que eso tiene sentido, sin embargo, el 99% de las veces no lo tiene.
- Si desea una clase auxiliar de SQL, tenga una clase que use una instancia de PDO detrás de escena. No juntes tu clase de ayuda y tu clase de conexión.
-
- Estás abusando de las excepciones
- ¿Cómo sabe un consumidor de su clase que se produjo una PDOException? Casi nunca deberías comer una excepción. Si sus métodos fallan, es probable que el código consumidor deba saberlo.
- Tu registro es defectuoso
- Si alguna vez utiliza una clase como
C::a();
C::b();
o si alguna vez se encuentra creando un nuevo objeto en un método, es probable que sea una señal de que la persona que llama debe proporcionar la dependencia.- Proporciona flexibilidad (¿qué pasa si alguien quiere tener dos instancias de su clase y usar dos registradores diferentes?)
- Facilita el acoplamiento (la
Database
depende secretamente dePDO
; no es suficiente decirle a los consumidores que descomenten el código si tienen un registrador llamadoLog
)
- Pase un registrador en su lugar que implemente una determinada interfaz
- Permite flexibilidad como un registrador nulo (un registrador que simplemente descarta todo) o como tener un registro de instancia en una tabla de base de datos y otro en un CSV
- Dependiendo del nivel bajo de su clase, es posible que un registrador no pertenezca allí.
- Lo que quiero decir con esto es que hay un cierto nivel en el que el registro simplemente no tiene sentido. Las llamadas al sistema no se registran. Incluso PDO no se registra. Probablemente debería manejar el registro en el terreno de la aplicación donde la aplicación puede tener una mejor idea de cómo se debe manejar el registro.
- Si alguna vez utiliza una clase como
- Tu constructor mata la flexibilidad
- SQL Server o Postgres, por ejemplo, tendrán DSN muy diferentes a MySQL
- al tratar de ser demasiado útil, su constructor ha eliminado esta flexibilidad: hay una razón por la que PDO toma un DSN y no los parámetros que toma su constructor.
- Abordan la persistencia, pero ¿qué pasa con los otros atributos?
- SQL Server o Postgres, por ejemplo, tendrán DSN muy diferentes a MySQL
- Algunas notas de estilista (nota: estas son 100% de opinión)
- Es bastante estándar aplicar sangría al primer nivel dentro de las clases.
-
switch:
yswitch:
endswitch;
se utilizan muy raramente. Mucha gente va a discutir con vehemencia conmigo sobre esto, pero creo que alendwhile;
endif;
y así sucesivamente, las construcciones son una mancha espantosa en el lenguaje C-esque. -
Null
casi siempre se escribeNULL
onull
en PHP.
En resumen, este es un buen comienzo, pero probablemente esté yendo por el camino equivocado con esta clase. Aquí hay muchas fallas técnicas y de diseño que deben abordarse. Sugeriría aprender un poco más la sintaxis de OO y tratar de familiarizarse mejor con las filosofías de bajo nivel y las ideas de programación orientada a objetos (cuando tenga más tiempo, volveré y proporcionaré algunos enlaces).