Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like TMappedStatement often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use TMappedStatement, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 35 | class TMappedStatement extends \Prado\TComponent implements IMappedStatement |
||
| 36 | { |
||
| 37 | /** |
||
| 38 | * @var TSqlMapStatement current SQL statement. |
||
| 39 | */ |
||
| 40 | private $_statement; |
||
| 41 | |||
| 42 | /** |
||
| 43 | * @var TPreparedCommand SQL command prepareer |
||
| 44 | */ |
||
| 45 | private $_command; |
||
| 46 | |||
| 47 | /** |
||
| 48 | * @var TSqlMapper sqlmap used by this mapper. |
||
| 49 | */ |
||
| 50 | private $_manager; |
||
| 51 | |||
| 52 | /** |
||
| 53 | * @var TPostSelectBinding[] post select statement queue. |
||
| 54 | */ |
||
| 55 | private $_selectQueue = []; |
||
| 56 | |||
| 57 | /** |
||
| 58 | * @var bool true when data is mapped to a particular row. |
||
| 59 | */ |
||
| 60 | private $_IsRowDataFound = false; |
||
| 61 | |||
| 62 | /** |
||
| 63 | * @var TSQLMapObjectCollectionTree group by object collection tree |
||
| 64 | */ |
||
| 65 | private $_groupBy; |
||
| 66 | |||
| 67 | /** |
||
| 68 | * @var Post select is to query for list. |
||
| 69 | */ |
||
| 70 | const QUERY_FOR_LIST = 0; |
||
| 71 | |||
| 72 | /** |
||
| 73 | * @var Post select is to query for list. |
||
| 74 | */ |
||
| 75 | const QUERY_FOR_ARRAY = 1; |
||
| 76 | |||
| 77 | /** |
||
| 78 | * @var Post select is to query for object. |
||
| 79 | */ |
||
| 80 | const QUERY_FOR_OBJECT = 2; |
||
| 81 | |||
| 82 | /** |
||
| 83 | * @return string Name used to identify the TMappedStatement amongst the others. |
||
| 84 | * This the name of the SQL statement by default. |
||
| 85 | */ |
||
| 86 | public function getID() |
||
| 90 | |||
| 91 | /** |
||
| 92 | * @return TSqlMapStatement The SQL statment used by this MappedStatement |
||
| 93 | */ |
||
| 94 | public function getStatement() |
||
| 98 | |||
| 99 | /** |
||
| 100 | * @return TSqlMapper The SqlMap used by this MappedStatement |
||
| 101 | */ |
||
| 102 | public function getManager() |
||
| 106 | |||
| 107 | /** |
||
| 108 | * @return TPreparedCommand command to prepare SQL statements. |
||
| 109 | */ |
||
| 110 | public function getCommand() |
||
| 114 | |||
| 115 | /** |
||
| 116 | * Empty the group by results cache. |
||
| 117 | */ |
||
| 118 | protected function initialGroupByResults() |
||
| 122 | |||
| 123 | /** |
||
| 124 | * Creates a new mapped statement. |
||
| 125 | * @param TSqlMapper $sqlMap an sqlmap. |
||
| 126 | * @param TSqlMapStatement $statement An SQL statement. |
||
| 127 | */ |
||
| 128 | public function __construct(TSqlMapManager $sqlMap, TSqlMapStatement $statement) |
||
| 135 | |||
| 136 | public function getSqlString() |
||
| 140 | |||
| 141 | /** |
||
| 142 | * Execute SQL Query. |
||
| 143 | * @param IDbConnection $connection database connection |
||
| 144 | * @param array $sql SQL statement and parameters. |
||
| 145 | * @throws TSqlMapExecutionException if execution error or false record set. |
||
| 146 | * @throws TSqlMapQueryExecutionException if any execution error |
||
| 147 | * @return mixed record set if applicable. |
||
| 148 | */ |
||
| 149 | /* protected function executeSQLQuery($connection, $sql) |
||
| 150 | { |
||
| 151 | try |
||
| 152 | { |
||
| 153 | if(!($recordSet = $connection->execute($sql['sql'],$sql['parameters']))) |
||
| 154 | { |
||
| 155 | throw new TSqlMapExecutionException( |
||
| 156 | 'sqlmap_execution_error_no_record', $this->getID(), |
||
| 157 | $connection->ErrorMsg()); |
||
| 158 | } |
||
| 159 | return $recordSet; |
||
| 160 | } |
||
| 161 | catch (Exception $e) |
||
| 162 | { |
||
| 163 | throw new TSqlMapQueryExecutionException($this->getStatement(), $e); |
||
| 164 | } |
||
| 165 | }*/ |
||
| 166 | |||
| 167 | /** |
||
| 168 | * Execute SQL Query with limits. |
||
| 169 | * @param IDbConnection $connection database connection |
||
| 170 | * @param $command |
||
| 171 | * @param int $max The maximum number of rows to return. |
||
| 172 | * @param int $skip The number of rows to skip over. |
||
| 173 | * @throws TSqlMapExecutionException if execution error or false record set. |
||
| 174 | * @throws TSqlMapQueryExecutionException if any execution error |
||
| 175 | * @return mixed record set if applicable. |
||
| 176 | */ |
||
| 177 | protected function executeSQLQueryLimit($connection, $command, $max, $skip) |
||
| 178 | { |
||
| 179 | if ($max > -1 || $skip > -1) { |
||
| 180 | $maxStr = $max > 0 ? ' LIMIT ' . $max : ''; |
||
| 181 | $skipStr = $skip > 0 ? ' OFFSET ' . $skip : ''; |
||
| 182 | $command->setText($command->getText() . $maxStr . $skipStr); |
||
| 183 | } |
||
| 184 | $connection->setActive(true); |
||
| 185 | return $command->query(); |
||
| 186 | |||
| 187 | /*//var_dump($command); |
||
| 188 | try |
||
| 189 | { |
||
| 190 | $recordSet = $connection->selectLimit($sql['sql'],$max,$skip,$sql['parameters']); |
||
| 191 | if(!$recordSet) |
||
| 192 | { |
||
| 193 | throw new TSqlMapExecutionException( |
||
| 194 | 'sqlmap_execution_error_query_for_list', |
||
| 195 | $connection->ErrorMsg()); |
||
| 196 | } |
||
| 197 | return $recordSet; |
||
| 198 | } |
||
| 199 | catch (Exception $e) |
||
| 200 | { |
||
| 201 | throw new TSqlMapQueryExecutionException($this->getStatement(), $e); |
||
| 202 | }*/ |
||
| 203 | } |
||
| 204 | |||
| 205 | /** |
||
| 206 | * Executes the SQL and retuns a List of result objects. |
||
| 207 | * @param IDbConnection $connection database connection |
||
| 208 | * @param mixed $parameter The object used to set the parameters in the SQL. |
||
| 209 | * @param null|object $result result collection object. |
||
| 210 | * @param int $skip The number of rows to skip over. |
||
| 211 | * @param int $max The maximum number of rows to return. |
||
| 212 | * @param null|callable $delegate row delegate handler |
||
| 213 | * @return array a list of result objects |
||
| 214 | * @see executeQueryForList() |
||
| 215 | */ |
||
| 216 | public function executeQueryForList($connection, $parameter, $result = null, $skip = -1, $max = -1, $delegate = null) |
||
| 217 | { |
||
| 218 | $sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter, $skip, $max); |
||
| 219 | return $this->runQueryForList($connection, $parameter, $sql, $result, $delegate); |
||
| 220 | } |
||
| 221 | |||
| 222 | /** |
||
| 223 | * Executes the SQL and retuns a List of result objects. |
||
| 224 | * |
||
| 225 | * This method should only be called by internal developers, consider using |
||
| 226 | * <tt>executeQueryForList()</tt> first. |
||
| 227 | * |
||
| 228 | * @param IDbConnection $connection database connection |
||
| 229 | * @param mixed $parameter The object used to set the parameters in the SQL. |
||
| 230 | * @param array $sql SQL string and subsititution parameters. |
||
| 231 | * @param object $result result collection object. |
||
| 232 | * @param null|callable $delegate row delegate handler |
||
| 233 | * @return array a list of result objects |
||
| 234 | * @see executeQueryForList() |
||
| 235 | */ |
||
| 236 | public function runQueryForList($connection, $parameter, $sql, $result, $delegate = null) |
||
| 237 | { |
||
| 238 | $registry = $this->getManager()->getTypeHandlers(); |
||
| 239 | $list = $result instanceof \ArrayAccess ? $result : |
||
| 240 | $this->_statement->createInstanceOfListClass($registry); |
||
| 241 | $connection->setActive(true); |
||
| 242 | $reader = $sql->query(); |
||
| 243 | //$reader = $this->executeSQLQueryLimit($connection, $sql, $max, $skip); |
||
| 244 | if ($delegate !== null) { |
||
| 245 | foreach ($reader as $row) { |
||
| 246 | $obj = $this->applyResultMap($row); |
||
| 247 | $param = new TResultSetListItemParameter($obj, $parameter, $list); |
||
| 248 | $this->raiseRowDelegate($delegate, $param); |
||
| 249 | } |
||
| 250 | } else { |
||
| 251 | //var_dump($sql,$parameter); |
||
| 252 | foreach ($reader as $row) { |
||
| 253 | // var_dump($row); |
||
| 254 | $list[] = $this->applyResultMap($row); |
||
| 255 | } |
||
| 256 | } |
||
| 257 | |||
| 258 | if (!$this->_groupBy->isEmpty()) { |
||
| 259 | $list = $this->_groupBy->collect(); |
||
| 260 | $this->initialGroupByResults(); |
||
| 261 | } |
||
| 262 | |||
| 263 | $this->executePostSelect($connection); |
||
| 264 | $this->onExecuteQuery($sql); |
||
| 265 | |||
| 266 | return $list; |
||
| 267 | } |
||
| 268 | |||
| 269 | /** |
||
| 270 | * Executes the SQL and retuns all rows selected in a map that is keyed on |
||
| 271 | * the property named in the keyProperty parameter. The value at each key |
||
| 272 | * will be the value of the property specified in the valueProperty parameter. |
||
| 273 | * If valueProperty is null, the entire result object will be entered. |
||
| 274 | * @param IDbConnection $connection database connection |
||
| 275 | * @param mixed $parameter The object used to set the parameters in the SQL. |
||
| 276 | * @param string $keyProperty The property of the result object to be used as the key. |
||
| 277 | * @param null|string $valueProperty The property of the result object to be used as the value (or null). |
||
| 278 | * @param int $skip The number of rows to skip over. |
||
| 279 | * @param int $max The maximum number of rows to return. |
||
| 280 | * @param null|callable $delegate row delegate handler |
||
| 281 | * @return array An array of object containing the rows keyed by keyProperty. |
||
| 282 | */ |
||
| 283 | public function executeQueryForMap($connection, $parameter, $keyProperty, $valueProperty = null, $skip = -1, $max = -1, $delegate = null) |
||
| 284 | { |
||
| 285 | $sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter, $skip, $max); |
||
| 286 | return $this->runQueryForMap($connection, $parameter, $sql, $keyProperty, $valueProperty, $delegate); |
||
| 287 | } |
||
| 288 | |||
| 289 | /** |
||
| 290 | * Executes the SQL and retuns all rows selected in a map that is keyed on |
||
| 291 | * the property named in the keyProperty parameter. The value at each key |
||
| 292 | * will be the value of the property specified in the valueProperty parameter. |
||
| 293 | * If valueProperty is null, the entire result object will be entered. |
||
| 294 | * |
||
| 295 | * This method should only be called by internal developers, consider using |
||
| 296 | * <tt>executeQueryForMap()</tt> first. |
||
| 297 | * |
||
| 298 | * @param IDbConnection $connection database connection |
||
| 299 | * @param mixed $parameter The object used to set the parameters in the SQL. |
||
| 300 | * @param mixed $command |
||
| 301 | * @param string $keyProperty The property of the result object to be used as the key. |
||
| 302 | * @param null|string $valueProperty The property of the result object to be used as the value (or null). |
||
| 303 | * @param null|callable $delegate row delegate, a callback function |
||
| 304 | * @return array An array of object containing the rows keyed by keyProperty. |
||
| 305 | * @see executeQueryForMap() |
||
| 306 | */ |
||
| 307 | public function runQueryForMap($connection, $parameter, $command, $keyProperty, $valueProperty = null, $delegate = null) |
||
| 308 | { |
||
| 309 | $map = []; |
||
| 310 | //$recordSet = $this->executeSQLQuery($connection, $sql); |
||
| 311 | $connection->setActive(true); |
||
| 312 | $reader = $command->query(); |
||
| 313 | if ($delegate !== null) { |
||
| 314 | //while($row = $recordSet->fetchRow()) |
||
| 315 | foreach ($reader as $row) { |
||
| 316 | $obj = $this->applyResultMap($row); |
||
| 317 | $key = TPropertyAccess::get($obj, $keyProperty); |
||
| 318 | $value = ($valueProperty === null) ? $obj : |
||
| 319 | TPropertyAccess::get($obj, $valueProperty); |
||
| 320 | $param = new TResultSetMapItemParameter($key, $value, $parameter, $map); |
||
| 321 | $this->raiseRowDelegate($delegate, $param); |
||
| 322 | } |
||
| 323 | } else { |
||
| 324 | //while($row = $recordSet->fetchRow()) |
||
| 325 | foreach ($reader as $row) { |
||
| 326 | $obj = $this->applyResultMap($row); |
||
| 327 | $key = TPropertyAccess::get($obj, $keyProperty); |
||
| 328 | $map[$key] = ($valueProperty === null) ? $obj : |
||
| 329 | TPropertyAccess::get($obj, $valueProperty); |
||
| 330 | } |
||
| 331 | } |
||
| 332 | $this->onExecuteQuery($command); |
||
| 333 | return $map; |
||
| 334 | } |
||
| 335 | |||
| 336 | /** |
||
| 337 | * Raises delegate handler. |
||
| 338 | * This method is invoked for each new list item. It is the responsibility |
||
| 339 | * of the handler to add the item to the list. |
||
| 340 | * @param object $handler event parameter |
||
| 341 | * @param mixed $param |
||
| 342 | */ |
||
| 343 | protected function raiseRowDelegate($handler, $param) |
||
| 344 | { |
||
| 345 | if (is_string($handler)) { |
||
| 346 | call_user_func($handler, $this, $param); |
||
| 347 | } elseif (is_callable($handler, true)) { |
||
| 348 | // an array: 0 - object, 1 - method name/path |
||
| 349 | list($object, $method) = $handler; |
||
| 350 | if (is_string($object)) { // static method call |
||
| 351 | call_user_func($handler, $this, $param); |
||
| 352 | View Code Duplication | } else { |
|
| 353 | if (($pos = strrpos($method, '.')) !== false) { |
||
| 354 | $object = $this->getSubProperty(substr($method, 0, $pos)); |
||
| 355 | $method = substr($method, $pos + 1); |
||
| 356 | } |
||
| 357 | $object->$method($this, $param); |
||
| 358 | } |
||
| 359 | } else { |
||
| 360 | throw new TInvalidDataValueException('sqlmap_invalid_delegate', $this->getID(), $handler); |
||
| 361 | } |
||
| 362 | } |
||
| 363 | |||
| 364 | /** |
||
| 365 | * Executes an SQL statement that returns a single row as an object of the |
||
| 366 | * type of the <tt>$result</tt> passed in as a parameter. |
||
| 367 | * @param IDbConnection $connection database connection |
||
| 368 | * @param mixed $parameter The parameter data (object, arrary, primitive) used to set the parameters in the SQL |
||
| 369 | * @param null|mixed $result The result object. |
||
| 370 | * @return object the object. |
||
| 371 | */ |
||
| 372 | public function executeQueryForObject($connection, $parameter, $result = null) |
||
| 373 | { |
||
| 374 | $sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter); |
||
| 375 | return $this->runQueryForObject($connection, $sql, $result); |
||
| 376 | } |
||
| 377 | |||
| 378 | /** |
||
| 379 | * Executes an SQL statement that returns a single row as an object of the |
||
| 380 | * type of the <tt>$result</tt> passed in as a parameter. |
||
| 381 | * |
||
| 382 | * This method should only be called by internal developers, consider using |
||
| 383 | * <tt>executeQueryForObject()</tt> first. |
||
| 384 | * |
||
| 385 | * @param IDbConnection $connection database connection |
||
| 386 | * @param $command |
||
| 387 | * @param object &$result The result object. |
||
| 388 | * @return object the object. |
||
| 389 | * @see executeQueryForObject() |
||
| 390 | */ |
||
| 391 | public function runQueryForObject($connection, $command, &$result) |
||
| 392 | { |
||
| 393 | $object = null; |
||
| 394 | $connection->setActive(true); |
||
| 395 | foreach ($command->query() as $row) { |
||
| 396 | $object = $this->applyResultMap($row, $result); |
||
| 397 | } |
||
| 398 | |||
| 399 | if (!$this->_groupBy->isEmpty()) { |
||
| 400 | $list = $this->_groupBy->collect(); |
||
| 401 | $this->initialGroupByResults(); |
||
| 402 | $object = $list[0]; |
||
| 403 | } |
||
| 404 | |||
| 405 | $this->executePostSelect($connection); |
||
| 406 | $this->onExecuteQuery($command); |
||
| 407 | |||
| 408 | return $object; |
||
| 409 | } |
||
| 410 | |||
| 411 | /** |
||
| 412 | * Execute an insert statement. Fill the parameter object with the ouput |
||
| 413 | * parameters if any, also could return the insert generated key. |
||
| 414 | * @param IDbConnection $connection database connection |
||
| 415 | * @param mixed $parameter The parameter object used to fill the statement. |
||
| 416 | * @return string the insert generated key. |
||
| 417 | */ |
||
| 418 | public function executeInsert($connection, $parameter) |
||
| 419 | { |
||
| 420 | $generatedKey = $this->getPreGeneratedSelectKey($connection, $parameter); |
||
| 421 | |||
| 422 | $command = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter); |
||
| 423 | // var_dump($command,$parameter); |
||
| 424 | $result = $command->execute(); |
||
| 425 | |||
| 426 | if ($generatedKey === null) { |
||
| 427 | $generatedKey = $this->getPostGeneratedSelectKey($connection, $parameter); |
||
| 428 | } |
||
| 429 | |||
| 430 | $this->executePostSelect($connection); |
||
| 431 | $this->onExecuteQuery($command); |
||
| 432 | return $generatedKey; |
||
| 433 | } |
||
| 434 | |||
| 435 | /** |
||
| 436 | * Gets the insert generated ID before executing an insert statement. |
||
| 437 | * @param IDbConnection $connection database connection |
||
| 438 | * @param mixed $parameter insert statement parameter. |
||
| 439 | * @return string new insert ID if pre-select key statement was executed, null otherwise. |
||
| 440 | */ |
||
| 441 | View Code Duplication | protected function getPreGeneratedSelectKey($connection, $parameter) |
|
| 442 | { |
||
| 443 | if ($this->_statement instanceof TSqlMapInsert) { |
||
| 444 | $selectKey = $this->_statement->getSelectKey(); |
||
| 445 | if (($selectKey !== null) && !$selectKey->getIsAfter()) { |
||
| 446 | return $this->executeSelectKey($connection, $parameter, $selectKey); |
||
| 447 | } |
||
| 448 | } |
||
| 449 | } |
||
| 450 | |||
| 451 | /** |
||
| 452 | * Gets the inserted row ID after executing an insert statement. |
||
| 453 | * @param IDbConnection $connection database connection |
||
| 454 | * @param mixed $parameter insert statement parameter. |
||
| 455 | * @return string last insert ID, null otherwise. |
||
| 456 | */ |
||
| 457 | View Code Duplication | protected function getPostGeneratedSelectKey($connection, $parameter) |
|
| 458 | { |
||
| 459 | if ($this->_statement instanceof TSqlMapInsert) { |
||
| 460 | $selectKey = $this->_statement->getSelectKey(); |
||
| 461 | if (($selectKey !== null) && $selectKey->getIsAfter()) { |
||
| 462 | return $this->executeSelectKey($connection, $parameter, $selectKey); |
||
| 463 | } |
||
| 464 | } |
||
| 465 | } |
||
| 466 | |||
| 467 | /** |
||
| 468 | * Execute the select key statement, used to obtain last insert ID. |
||
| 469 | * @param IDbConnection $connection database connection |
||
| 470 | * @param mixed $parameter insert statement parameter |
||
| 471 | * @param TSqlMapSelectKey $selectKey select key statement |
||
| 472 | * @return string last insert ID. |
||
| 473 | */ |
||
| 474 | protected function executeSelectKey($connection, $parameter, $selectKey) |
||
| 475 | { |
||
| 476 | $mappedStatement = $this->getManager()->getMappedStatement($selectKey->getID()); |
||
| 477 | $generatedKey = $mappedStatement->executeQueryForObject( |
||
| 478 | $connection, |
||
| 479 | $parameter, |
||
| 480 | null |
||
| 481 | ); |
||
| 482 | if (strlen($prop = $selectKey->getProperty()) > 0) { |
||
| 483 | TPropertyAccess::set($parameter, $prop, $generatedKey); |
||
| 484 | } |
||
| 485 | return $generatedKey; |
||
| 486 | } |
||
| 487 | |||
| 488 | /** |
||
| 489 | * Execute an update statement. Also used for delete statement. |
||
| 490 | * Return the number of rows effected. |
||
| 491 | * @param IDbConnection $connection database connection |
||
| 492 | * @param mixed $parameter The object used to set the parameters in the SQL. |
||
| 493 | * @return int The number of rows effected. |
||
| 494 | */ |
||
| 495 | public function executeUpdate($connection, $parameter) |
||
| 496 | { |
||
| 497 | $sql = $this->_command->create($this->getManager(), $connection, $this->_statement, $parameter); |
||
| 498 | $affectedRows = $sql->execute(); |
||
| 499 | //$this->executeSQLQuery($connection, $sql); |
||
| 500 | $this->executePostSelect($connection); |
||
| 501 | $this->onExecuteQuery($sql); |
||
| 502 | return $affectedRows; |
||
| 503 | } |
||
| 504 | |||
| 505 | /** |
||
| 506 | * Process 'select' result properties |
||
| 507 | * @param IDbConnection $connection database connection |
||
| 508 | */ |
||
| 509 | protected function executePostSelect($connection) |
||
| 510 | { |
||
| 511 | while (count($this->_selectQueue)) { |
||
| 512 | $postSelect = array_shift($this->_selectQueue); |
||
| 513 | $method = $postSelect->getMethod(); |
||
| 514 | $statement = $postSelect->getStatement(); |
||
| 515 | $property = $postSelect->getResultProperty()->getProperty(); |
||
| 516 | $keys = $postSelect->getKeys(); |
||
| 517 | $resultObject = $postSelect->getResultObject(); |
||
| 518 | |||
| 519 | if ($method == self::QUERY_FOR_LIST || $method == self::QUERY_FOR_ARRAY) { |
||
| 520 | $values = $statement->executeQueryForList($connection, $keys, null); |
||
| 521 | |||
| 522 | if ($method == self::QUERY_FOR_ARRAY) { |
||
| 523 | $values = $values->toArray(); |
||
| 524 | } |
||
| 525 | TPropertyAccess::set($resultObject, $property, $values); |
||
| 526 | } elseif ($method == self::QUERY_FOR_OBJECT) { |
||
| 527 | $value = $statement->executeQueryForObject($connection, $keys, null); |
||
| 528 | TPropertyAccess::set($resultObject, $property, $value); |
||
| 529 | } |
||
| 530 | } |
||
| 531 | } |
||
| 532 | |||
| 533 | /** |
||
| 534 | * Raise the execute query event. |
||
| 535 | * @param array $sql prepared SQL statement and subsititution parameters |
||
| 536 | */ |
||
| 537 | public function onExecuteQuery($sql) |
||
| 538 | { |
||
| 539 | $this->raiseEvent('OnExecuteQuery', $this, $sql); |
||
| 540 | } |
||
| 541 | |||
| 542 | /** |
||
| 543 | * Apply result mapping. |
||
| 544 | * @param array $row a result set row retrieved from the database |
||
| 545 | * @param null|&object $resultObject the result object, will create if necessary. |
||
| 546 | * @return object the result filled with data, null if not filled. |
||
| 547 | */ |
||
| 548 | protected function applyResultMap($row, &$resultObject = null) |
||
| 549 | { |
||
| 550 | if ($row === false) { |
||
| 551 | return null; |
||
| 552 | } |
||
| 553 | |||
| 554 | $resultMapName = $this->_statement->getResultMap(); |
||
| 555 | $resultClass = $this->_statement->getResultClass(); |
||
| 556 | |||
| 557 | $obj = null; |
||
| 558 | if ($this->getManager()->getResultMaps()->contains($resultMapName)) { |
||
| 559 | $obj = $this->fillResultMap($resultMapName, $row, null, $resultObject); |
||
| 560 | } elseif (strlen($resultClass) > 0) { |
||
| 561 | $obj = $this->fillResultClass($resultClass, $row, $resultObject); |
||
| 562 | } else { |
||
| 563 | $obj = $this->fillDefaultResultMap(null, $row, $resultObject); |
||
| 564 | } |
||
| 565 | if (class_exists('TActiveRecord', false) && $obj instanceof TActiveRecord) { |
||
| 566 | //Create a new clean active record. |
||
| 567 | $obj = TActiveRecord::createRecord(get_class($obj), $obj); |
||
| 568 | } |
||
| 569 | return $obj; |
||
| 570 | } |
||
| 571 | |||
| 572 | /** |
||
| 573 | * Fill the result using ResultClass, will creates new result object if required. |
||
| 574 | * @param string $resultClass result object class name |
||
| 575 | * @param array $row a result set row retrieved from the database |
||
| 576 | * @param object $resultObject the result object, will create if necessary. |
||
| 577 | * @return object result object filled with data |
||
| 578 | */ |
||
| 579 | protected function fillResultClass($resultClass, $row, $resultObject) |
||
| 580 | { |
||
| 581 | if ($resultObject === null) { |
||
| 582 | $registry = $this->getManager()->getTypeHandlers(); |
||
| 583 | $resultObject = $this->_statement->createInstanceOfResultClass($registry, $row); |
||
| 584 | } |
||
| 585 | |||
| 586 | if ($resultObject instanceof \ArrayAccess) { |
||
| 587 | return $this->fillResultArrayList($row, $resultObject); |
||
| 588 | } elseif (is_object($resultObject)) { |
||
| 589 | return $this->fillResultObjectProperty($row, $resultObject); |
||
| 590 | } else { |
||
| 591 | return $this->fillDefaultResultMap(null, $row, $resultObject); |
||
| 592 | } |
||
| 593 | } |
||
| 594 | |||
| 595 | /** |
||
| 596 | * Apply the result to a TList or an array. |
||
| 597 | * @param array $row a result set row retrieved from the database |
||
| 598 | * @param object $resultObject result object, array or list |
||
| 599 | * @return object result filled with data. |
||
| 600 | */ |
||
| 601 | protected function fillResultArrayList($row, $resultObject) |
||
| 602 | { |
||
| 603 | if ($resultObject instanceof TList) { |
||
| 604 | foreach ($row as $v) { |
||
| 605 | $resultObject[] = $v; |
||
| 606 | } |
||
| 607 | } else { |
||
| 608 | foreach ($row as $k => $v) { |
||
| 609 | $resultObject[$k] = $v; |
||
| 610 | } |
||
| 611 | } |
||
| 612 | return $resultObject; |
||
| 613 | } |
||
| 614 | |||
| 615 | /** |
||
| 616 | * Apply the result to an object. |
||
| 617 | * @param array $row a result set row retrieved from the database |
||
| 618 | * @param object $resultObject result object, array or list |
||
| 619 | * @return object result filled with data. |
||
| 620 | */ |
||
| 621 | protected function fillResultObjectProperty($row, $resultObject) |
||
| 622 | { |
||
| 623 | $index = 0; |
||
| 624 | $registry = $this->getManager()->getTypeHandlers(); |
||
| 625 | foreach ($row as $k => $v) { |
||
| 626 | $property = new TResultProperty; |
||
| 627 | if (is_string($k) && strlen($k) > 0) { |
||
| 628 | $property->setColumn($k); |
||
| 629 | } |
||
| 630 | $property->setColumnIndex(++$index); |
||
| 631 | $type = gettype(TPropertyAccess::get($resultObject, $k)); |
||
| 632 | $property->setType($type); |
||
| 633 | $value = $property->getPropertyValue($registry, $row); |
||
| 634 | TPropertyAccess::set($resultObject, $k, $value); |
||
| 635 | } |
||
| 636 | return $resultObject; |
||
| 637 | } |
||
| 638 | |||
| 639 | /** |
||
| 640 | * Fills the result object according to result mappings. |
||
| 641 | * @param string $resultMapName result map name. |
||
| 642 | * @param array $row a result set row retrieved from the database |
||
| 643 | * @param null|mixed $parentGroup |
||
| 644 | * @param null|&object $resultObject result object to fill, will create new instances if required. |
||
| 645 | * @return object result object filled with data. |
||
| 646 | */ |
||
| 647 | protected function fillResultMap($resultMapName, $row, $parentGroup = null, &$resultObject = null) |
||
| 648 | { |
||
| 649 | $resultMap = $this->getManager()->getResultMap($resultMapName); |
||
| 650 | $registry = $this->getManager()->getTypeHandlers(); |
||
| 651 | $resultMap = $resultMap->resolveSubMap($registry, $row); |
||
| 652 | |||
| 653 | if ($resultObject === null) { |
||
| 654 | $resultObject = $resultMap->createInstanceOfResult($registry); |
||
| 655 | } |
||
| 656 | |||
| 657 | if (is_object($resultObject)) { |
||
| 658 | if (strlen($resultMap->getGroupBy()) > 0) { |
||
| 659 | return $this->addResultMapGroupBy($resultMap, $row, $parentGroup, $resultObject); |
||
| 660 | } else { |
||
| 661 | foreach ($resultMap->getColumns() as $property) { |
||
| 662 | $this->setObjectProperty($resultMap, $property, $row, $resultObject); |
||
| 663 | } |
||
| 664 | } |
||
| 665 | } else { |
||
| 666 | $resultObject = $this->fillDefaultResultMap($resultMap, $row, $resultObject); |
||
| 667 | } |
||
| 668 | return $resultObject; |
||
| 669 | } |
||
| 670 | |||
| 671 | /** |
||
| 672 | * ResultMap with GroupBy property. Save object collection graph in a tree |
||
| 673 | * and collect the result later. |
||
| 674 | * @param TResultMap $resultMap result mapping details. |
||
| 675 | * @param array $row a result set row retrieved from the database |
||
| 676 | * @param mixed $parent |
||
| 677 | * @param object &$resultObject the result object |
||
| 678 | * @return object result object. |
||
| 679 | */ |
||
| 680 | protected function addResultMapGroupBy($resultMap, $row, $parent, &$resultObject) |
||
| 681 | { |
||
| 682 | $group = $this->getResultMapGroupKey($resultMap, $row); |
||
| 683 | |||
| 684 | if (empty($parent)) { |
||
| 685 | $rootObject = ['object' => $resultObject, 'property' => null]; |
||
| 686 | $this->_groupBy->add(null, $group, $rootObject); |
||
| 687 | } |
||
| 688 | |||
| 689 | foreach ($resultMap->getColumns() as $property) { |
||
| 690 | //set properties. |
||
| 691 | $this->setObjectProperty($resultMap, $property, $row, $resultObject); |
||
| 692 | $nested = $property->getResultMapping(); |
||
| 693 | |||
| 694 | //nested property |
||
| 695 | if ($this->getManager()->getResultMaps()->contains($nested)) { |
||
| 696 | $nestedMap = $this->getManager()->getResultMap($nested); |
||
| 697 | $groupKey = $this->getResultMapGroupKey($nestedMap, $row); |
||
| 698 | |||
| 699 | //add the node reference first |
||
| 700 | if (empty($parent)) { |
||
| 701 | $this->_groupBy->add($group, $groupKey, ''); |
||
| 702 | } |
||
| 703 | |||
| 704 | //get the nested result mapping value |
||
| 705 | $value = $this->fillResultMap($nested, $row, $groupKey); |
||
| 706 | |||
| 707 | //add it to the object tree graph |
||
| 708 | $groupObject = ['object' => $value, 'property' => $property->getProperty()]; |
||
| 709 | if (empty($parent)) { |
||
| 710 | $this->_groupBy->add($group, $groupKey, $groupObject); |
||
| 711 | } else { |
||
| 712 | $this->_groupBy->add($parent, $groupKey, $groupObject); |
||
| 713 | } |
||
| 714 | } |
||
| 715 | } |
||
| 716 | return $resultObject; |
||
| 717 | } |
||
| 718 | |||
| 719 | /** |
||
| 720 | * Gets the result 'group by' groupping key for each row. |
||
| 721 | * @param TResultMap $resultMap result mapping details. |
||
| 722 | * @param array $row a result set row retrieved from the database |
||
| 723 | * @return string groupping key. |
||
| 724 | */ |
||
| 725 | protected function getResultMapGroupKey($resultMap, $row) |
||
| 726 | { |
||
| 727 | $groupBy = $resultMap->getGroupBy(); |
||
| 728 | if (isset($row[$groupBy])) { |
||
| 729 | return $resultMap->getID() . $row[$groupBy]; |
||
| 730 | } else { |
||
| 731 | return $resultMap->getID() . crc32(serialize($row)); |
||
| 732 | } |
||
| 733 | } |
||
| 734 | |||
| 735 | /** |
||
| 736 | * Fill the result map using default settings. If <tt>$resultMap</tt> is null |
||
| 737 | * the result object returned will be guessed from <tt>$resultObject</tt>. |
||
| 738 | * @param TResultMap $resultMap result mapping details. |
||
| 739 | * @param array $row a result set row retrieved from the database |
||
| 740 | * @param object $resultObject the result object |
||
| 741 | * @return mixed the result object filled with data. |
||
| 742 | */ |
||
| 743 | protected function fillDefaultResultMap($resultMap, $row, $resultObject) |
||
| 744 | { |
||
| 745 | if ($resultObject === null) { |
||
| 746 | $resultObject = ''; |
||
| 747 | } |
||
| 748 | |||
| 749 | if ($resultMap !== null) { |
||
| 750 | $result = $this->fillArrayResultMap($resultMap, $row, $resultObject); |
||
| 751 | } else { |
||
| 752 | $result = $row; |
||
| 753 | } |
||
| 754 | |||
| 755 | //if scalar result types |
||
| 756 | if (count($result) == 1 && ($type = gettype($resultObject)) != 'array') { |
||
| 757 | return $this->getScalarResult($result, $type); |
||
| 758 | } else { |
||
| 759 | return $result; |
||
| 760 | } |
||
| 761 | } |
||
| 762 | |||
| 763 | /** |
||
| 764 | * Retrieve the result map as an array. |
||
| 765 | * @param TResultMap $resultMap result mapping details. |
||
| 766 | * @param array $row a result set row retrieved from the database |
||
| 767 | * @param object $resultObject the result object |
||
| 768 | * @return array array list of result objects. |
||
| 769 | */ |
||
| 770 | protected function fillArrayResultMap($resultMap, $row, $resultObject) |
||
| 783 | |||
| 784 | /** |
||
| 785 | * Converts the first array value to scalar value of given type. |
||
| 786 | * @param array $result list of results |
||
| 787 | * @param string $type scalar type. |
||
| 788 | * @return mixed scalar value. |
||
| 789 | */ |
||
| 790 | protected function getScalarResult($result, $type) |
||
| 796 | |||
| 797 | /** |
||
| 798 | * Set a property of the result object with appropriate value. |
||
| 799 | * @param TResultMap $resultMap result mapping details. |
||
| 800 | * @param TResultProperty $property the result property to fill. |
||
| 801 | * @param array $row a result set row retrieved from the database |
||
| 802 | * @param object &$resultObject the result object |
||
| 803 | */ |
||
| 804 | protected function setObjectProperty($resultMap, $property, $row, &$resultObject) |
||
| 805 | { |
||
| 806 | $select = $property->getSelect(); |
||
| 807 | $key = $property->getProperty(); |
||
| 808 | $nested = $property->getNestedResultMap(); |
||
| 809 | $registry = $this->getManager()->getTypeHandlers(); |
||
| 810 | if ($key === '') { |
||
| 811 | $resultObject = $property->getPropertyValue($registry, $row); |
||
| 812 | } elseif (strlen($select) == 0 && ($nested === null)) { |
||
| 813 | $value = $property->getPropertyValue($registry, $row); |
||
| 814 | |||
| 815 | $this->_IsRowDataFound = $this->_IsRowDataFound || ($value != null); |
||
| 816 | if (is_array($resultObject) || is_object($resultObject)) { |
||
| 817 | TPropertyAccess::set($resultObject, $key, $value); |
||
| 818 | } else { |
||
| 819 | $resultObject = $value; |
||
| 842 | |||
| 843 | /** |
||
| 844 | * Add nested result property to post select queue. |
||
| 845 | * @param string $select post select statement ID |
||
| 846 | * @param TResultMap $resultMap current result mapping details. |
||
| 847 | * @param TResultProperty $property current result property. |
||
| 848 | * @param array $row a result set row retrieved from the database |
||
| 849 | * @param object $resultObject the result object |
||
| 850 | */ |
||
| 851 | protected function enquequePostSelect($select, $resultMap, $property, $row, $resultObject) |
||
| 884 | |||
| 885 | /** |
||
| 886 | * Finds in the post select property the SQL statement primary selection keys. |
||
| 887 | * @param TResultMap $resultMap result mapping details |
||
| 888 | * @param TResultProperty $property result property |
||
| 889 | * @param array $row current row data. |
||
| 890 | * @return array list of primary key values. |
||
| 891 | */ |
||
| 892 | protected function getPostSelectKeys($resultMap, $property, $row) |
||
| 907 | |||
| 908 | /** |
||
| 909 | * Fills the property with result mapping results. |
||
| 910 | * @param TResultMap $resultMap nested result mapping details. |
||
| 911 | * @param array $row a result set row retrieved from the database |
||
| 912 | * @param object &$resultObject the result object |
||
| 913 | * @return bool true if the data was found, false otherwise. |
||
| 914 | */ |
||
| 915 | protected function fillPropertyWithResultMap($resultMap, $row, &$resultObject) |
||
| 926 | |||
| 927 | public function __wakeup() |
||
| 933 | |||
| 934 | View Code Duplication | public function __sleep() |
|
| 949 | } |
||
| 950 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.