Skip to content

Fix IBM DB2 implementation / ibm_db2 driver #447

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Dec 20, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
# Upgrade to 2.5

No BC breaks yet.
## Support for pdo_ibm driver removed

The ``pdo_ibm`` driver is buggy and does not work well with Doctrine. Therefore it will no
longer be supported and has been removed from the ``Doctrine\DBAL\DriverManager`` drivers
map. It is highly encouraged to to use `ibm_db2` driver instead if you want to connect
to an IBM DB2 database as it is much more stable and secure.

If for some reason you have to utilize the ``pdo_ibm`` driver you can still use the `driverClass`
connection parameter to explicitly specify the ``Doctrine\DBAL\Driver\PDOIbm\Driver`` class.
However be aware that you are doing this at your own risk and it will not be guaranteed that
Doctrine will work as expected.

# Upgrade to 2.4

Expand Down
115 changes: 112 additions & 3 deletions lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ class DB2Statement implements \IteratorAggregate, Statement
*/
private $_bindParam = array();

/**
* @var string Name of the default class to instantiate when fetch mode is \PDO::FETCH_CLASS.
*/
private $defaultFetchClass = '\stdClass';

/**
* @var string Constructor arguments for the default class to instantiate when fetch mode is \PDO::FETCH_CLASS.
*/
private $defaultFetchClassCtorArgs = array();

/**
* @var integer
*/
Expand Down Expand Up @@ -165,7 +175,9 @@ public function execute($params = null)
*/
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
{
$this->_defaultFetchMode = $fetchMode;
$this->_defaultFetchMode = $fetchMode;
$this->defaultFetchClass = $arg2 ? $arg2 : $this->defaultFetchClass;
$this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;

return true;
}
Expand All @@ -191,8 +203,27 @@ public function fetch($fetchMode = null)
return db2_fetch_both($this->_stmt);
case \PDO::FETCH_ASSOC:
return db2_fetch_assoc($this->_stmt);
case \PDO::FETCH_CLASS:
$className = $this->defaultFetchClass;
$ctorArgs = $this->defaultFetchClassCtorArgs;

if (func_num_args() >= 2) {
$args = func_get_args();
$className = $args[1];
$ctorArgs = isset($args[2]) ? $args[2] : array();
}

$result = db2_fetch_object($this->_stmt);

if ($result instanceof \stdClass) {
$result = $this->castObject($result, $className, $ctorArgs);
}

return $result;
case \PDO::FETCH_NUM:
return db2_fetch_array($this->_stmt);
case PDO::FETCH_OBJ:
return db2_fetch_object($this->_stmt);
default:
throw new DB2Exception("Given Fetch-Style " . $fetchMode . " is not supported.");
}
Expand All @@ -204,8 +235,22 @@ public function fetch($fetchMode = null)
public function fetchAll($fetchMode = null)
{
$rows = array();
while ($row = $this->fetch($fetchMode)) {
$rows[] = $row;

switch ($fetchMode) {
case \PDO::FETCH_CLASS:
while ($row = call_user_func_array(array($this, 'fetch'), func_get_args())) {
$rows[] = $row;
}
break;
case \PDO::FETCH_COLUMN:
while ($row = $this->fetchColumn()) {
$rows[] = $row;
}
break;
default:
while ($row = $this->fetch($fetchMode)) {
$rows[] = $row;
}
}

return $rows;
Expand All @@ -231,4 +276,68 @@ public function rowCount()
{
return (@db2_num_rows($this->_stmt))?:0;
}

/**
* Casts a stdClass object to the given class name mapping its' properties.
*
* @param \stdClass $sourceObject Object to cast from.
* @param string|object $destinationClass Name of the class or class instance to cast to.
* @param array $ctorArgs Arguments to use for constructing the destination class instance.
*
* @return object
*
* @throws DB2Exception
*/
private function castObject(\stdClass $sourceObject, $destinationClass, array $ctorArgs = array())
{
if ( ! is_string($destinationClass)) {
if ( ! is_object($destinationClass)) {
throw new DB2Exception(sprintf(
'Destination class has to be of type string or object, %s given.', gettype($destinationClass)
));
}
} else {
$destinationClass = new \ReflectionClass($destinationClass);
$destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
}

$sourceReflection = new \ReflectionObject($sourceObject);
$destinationClassReflection = new \ReflectionObject($destinationClass);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks wrong when $destinationClass was passed as string, as it was then replaced by a ReflectionClass, and you are creating a ReflectionObject for a ReflectionClass instance here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arf no, sorry. It is replaced a second time. The naming of variables is really confusing on this method

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stof Yeah this is far from perfect. I have copied and modified this from my SQL Anywhere driver implementation which I coded long time ago and my knowlegde of reflection was not as good back then ;)

/** @var \ReflectionProperty[] $destinationProperties */
$destinationProperties = array_change_key_case($destinationClassReflection->getProperties(), \CASE_LOWER);

foreach ($sourceReflection->getProperties() as $sourceProperty) {
$sourceProperty->setAccessible(true);

$name = $sourceProperty->getName();
$value = $sourceProperty->getValue($sourceObject);

// Try to find a case-matching property.
if ($destinationClassReflection->hasProperty($name)) {
$destinationProperty = $destinationClassReflection->getProperty($name);

$destinationProperty->setAccessible(true);
$destinationProperty->setValue($destinationClass, $value);

continue;
}

$name = strtolower($name);

// Try to find a property without matching case.
// Fallback for the driver returning either all uppercase or all lowercase column names.
if (isset($destinationProperties[$name])) {
$destinationProperty = $destinationProperties[$name];

$destinationProperty->setAccessible(true);
$destinationProperty->setValue($destinationClass, $value);

continue;
}

$destinationClass->$name = $value;
}

return $destinationClass;
}
}
2 changes: 0 additions & 2 deletions lib/Doctrine/DBAL/DriverManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ final class DriverManager
'pdo_oci' => 'Doctrine\DBAL\Driver\PDOOracle\Driver',
'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver',
'ibm_db2' => 'Doctrine\DBAL\Driver\IBMDB2\DB2Driver',
'pdo_ibm' => 'Doctrine\DBAL\Driver\PDOIbm\Driver',
'pdo_sqlsrv' => 'Doctrine\DBAL\Driver\PDOSqlsrv\Driver',
'mysqli' => 'Doctrine\DBAL\Driver\Mysqli\Driver',
'drizzle_pdo_mysql' => 'Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver',
Expand Down Expand Up @@ -73,7 +72,6 @@ private function __construct()
* pdo_pgsql
* pdo_oci (unstable)
* pdo_sqlsrv
* pdo_ibm (unstable)
* pdo_sqlsrv
* mysqli
* sqlanywhere
Expand Down
Loading