Abstract.php
| 1 | <?php |
| 2 | |
| 3 | namespace { |
| 4 | /** |
| 5 | * Zend Framework |
| 6 | * |
| 7 | * LICENSE |
| 8 | * |
| 9 | * This source file is subject to the new BSD license that is bundled |
| 10 | * with this package in the file LICENSE.txt. |
| 11 | * It is also available through the world-wide-web at this URL: |
| 12 | * http://framework.zend.com/license/new-bsd |
| 13 | * If you did not receive a copy of the license and are unable to |
| 14 | * obtain it through the world-wide-web, please send an email |
| 15 | * to license@zend.com so we can send you a copy immediately. |
| 16 | * |
| 17 | * @category Zend |
| 18 | * @package Zend_Db |
| 19 | * @subpackage Table |
| 20 | * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) |
| 21 | * @license http://framework.zend.com/license/new-bsd New BSD License |
| 22 | * @version $Id: Abstract.php 23775 2011-03-01 17:25:24Z ralph $ |
| 23 | */ |
| 24 | /** |
| 25 | * @see Zend_Db |
| 26 | */ |
| 27 | // require_once 'Zend/Db.php'; |
| 28 | /** |
| 29 | * @category Zend |
| 30 | * @package Zend_Db |
| 31 | * @subpackage Table |
| 32 | * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) |
| 33 | * @license http://framework.zend.com/license/new-bsd New BSD License |
| 34 | */ |
| 35 | abstract class Zend_Db_Table_Row_Abstract implements \ArrayAccess, \IteratorAggregate |
| 36 | { |
| 37 | /** |
| 38 | * The data for each column in the row (column_name => value). |
| 39 | * The keys must match the physical names of columns in the |
| 40 | * table for which this row is defined. |
| 41 | * |
| 42 | * @var array |
| 43 | */ |
| 44 | protected $_data = array(); |
| 45 | /** |
| 46 | * This is set to a copy of $_data when the data is fetched from |
| 47 | * a database, specified as a new tuple in the constructor, or |
| 48 | * when dirty data is posted to the database with save(). |
| 49 | * |
| 50 | * @var array |
| 51 | */ |
| 52 | protected $_cleanData = array(); |
| 53 | /** |
| 54 | * Tracks columns where data has been updated. Allows more specific insert and |
| 55 | * update operations. |
| 56 | * |
| 57 | * @var array |
| 58 | */ |
| 59 | protected $_modifiedFields = array(); |
| 60 | /** |
| 61 | * Zend_Db_Table_Abstract parent class or instance. |
| 62 | * |
| 63 | * @var Zend_Db_Table_Abstract |
| 64 | */ |
| 65 | protected $_table = null; |
| 66 | /** |
| 67 | * Connected is true if we have a reference to a live |
| 68 | * Zend_Db_Table_Abstract object. |
| 69 | * This is false after the Rowset has been deserialized. |
| 70 | * |
| 71 | * @var boolean |
| 72 | */ |
| 73 | protected $_connected = \true; |
| 74 | /** |
| 75 | * A row is marked read only if it contains columns that are not physically represented within |
| 76 | * the database schema (e.g. evaluated columns/Zend_Db_Expr columns). This can also be passed |
| 77 | * as a run-time config options as a means of protecting row data. |
| 78 | * |
| 79 | * @var boolean |
| 80 | */ |
| 81 | protected $_readOnly = \false; |
| 82 | /** |
| 83 | * Name of the class of the Zend_Db_Table_Abstract object. |
| 84 | * |
| 85 | * @var string |
| 86 | */ |
| 87 | protected $_tableClass = null; |
| 88 | /** |
| 89 | * Primary row key(s). |
| 90 | * |
| 91 | * @var array |
| 92 | */ |
| 93 | protected $_primary; |
| 94 | /** |
| 95 | * Constructor. |
| 96 | * |
| 97 | * Supported params for $config are:- |
| 98 | * - table = class name or object of type Zend_Db_Table_Abstract |
| 99 | * - data = values of columns in this row. |
| 100 | * |
| 101 | * @param array $config OPTIONAL Array of user-specified config options. |
| 102 | * @return void |
| 103 | * @throws Zend_Db_Table_Row_Exception |
| 104 | */ |
| 105 | public function __construct(array $config = array()) |
| 106 | { |
| 107 | if (isset($config['table']) && $config['table'] instanceof \Zend_Db_Table_Abstract) { |
| 108 | $this->_table = $config['table']; |
| 109 | $this->_tableClass = \get_class($this->_table); |
| 110 | } elseif ($this->_tableClass !== null) { |
| 111 | $this->_table = $this->_getTableFromString($this->_tableClass); |
| 112 | } |
| 113 | if (isset($config['data'])) { |
| 114 | if (!\is_array($config['data'])) { |
| 115 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 116 | throw new \Zend_Db_Table_Row_Exception('Data must be an array'); |
| 117 | } |
| 118 | $this->_data = $config['data']; |
| 119 | } |
| 120 | if (isset($config['stored']) && $config['stored'] === \true) { |
| 121 | $this->_cleanData = $this->_data; |
| 122 | } |
| 123 | if (isset($config['readOnly']) && $config['readOnly'] === \true) { |
| 124 | $this->setReadOnly(\true); |
| 125 | } |
| 126 | // Retrieve primary keys from table schema |
| 127 | if ($table = $this->_getTable()) { |
| 128 | $info = $table->info(); |
| 129 | $this->_primary = (array) $info['primary']; |
| 130 | } |
| 131 | $this->init(); |
| 132 | } |
| 133 | /** |
| 134 | * Transform a column name from the user-specified form |
| 135 | * to the physical form used in the database. |
| 136 | * You can override this method in a custom Row class |
| 137 | * to implement column name mappings, for example inflection. |
| 138 | * |
| 139 | * @param string $columnName Column name given. |
| 140 | * @return string The column name after transformation applied (none by default). |
| 141 | * @throws Zend_Db_Table_Row_Exception if the $columnName is not a string. |
| 142 | */ |
| 143 | protected function _transformColumn($columnName) |
| 144 | { |
| 145 | if (!\is_string($columnName)) { |
| 146 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 147 | throw new \Zend_Db_Table_Row_Exception('Specified column is not a string'); |
| 148 | } |
| 149 | // Perform no transformation by default |
| 150 | return $columnName; |
| 151 | } |
| 152 | /** |
| 153 | * Retrieve row field value |
| 154 | * |
| 155 | * @param string $columnName The user-specified column name. |
| 156 | * @return string The corresponding column value. |
| 157 | * @throws Zend_Db_Table_Row_Exception if the $columnName is not a column in the row. |
| 158 | */ |
| 159 | public function __get($columnName) |
| 160 | { |
| 161 | $columnName = $this->_transformColumn($columnName); |
| 162 | if (!\array_key_exists($columnName, $this->_data)) { |
| 163 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 164 | throw new \Zend_Db_Table_Row_Exception("Specified column \"{$columnName}\" is not in the row"); |
| 165 | } |
| 166 | return $this->_data[$columnName]; |
| 167 | } |
| 168 | /** |
| 169 | * Set row field value |
| 170 | * |
| 171 | * @param string $columnName The column key. |
| 172 | * @param mixed $value The value for the property. |
| 173 | * @return void |
| 174 | * @throws Zend_Db_Table_Row_Exception |
| 175 | */ |
| 176 | public function __set($columnName, $value) |
| 177 | { |
| 178 | $columnName = $this->_transformColumn($columnName); |
| 179 | if (!\array_key_exists($columnName, $this->_data)) { |
| 180 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 181 | throw new \Zend_Db_Table_Row_Exception("Specified column \"{$columnName}\" is not in the row"); |
| 182 | } |
| 183 | $this->_data[$columnName] = $value; |
| 184 | $this->_modifiedFields[$columnName] = \true; |
| 185 | } |
| 186 | /** |
| 187 | * Unset row field value |
| 188 | * |
| 189 | * @param string $columnName The column key. |
| 190 | * @return Zend_Db_Table_Row_Abstract |
| 191 | * @throws Zend_Db_Table_Row_Exception |
| 192 | */ |
| 193 | public function __unset($columnName) |
| 194 | { |
| 195 | $columnName = $this->_transformColumn($columnName); |
| 196 | if (!\array_key_exists($columnName, $this->_data)) { |
| 197 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 198 | throw new \Zend_Db_Table_Row_Exception("Specified column \"{$columnName}\" is not in the row"); |
| 199 | } |
| 200 | if ($this->isConnected() && \in_array($columnName, $this->_table->info('primary'))) { |
| 201 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 202 | throw new \Zend_Db_Table_Row_Exception("Specified column \"{$columnName}\" is a primary key and should not be unset"); |
| 203 | } |
| 204 | unset($this->_data[$columnName]); |
| 205 | return $this; |
| 206 | } |
| 207 | /** |
| 208 | * Test existence of row field |
| 209 | * |
| 210 | * @param string $columnName The column key. |
| 211 | * @return boolean |
| 212 | */ |
| 213 | public function __isset($columnName) |
| 214 | { |
| 215 | $columnName = $this->_transformColumn($columnName); |
| 216 | return \array_key_exists($columnName, $this->_data); |
| 217 | } |
| 218 | /** |
| 219 | * Store table, primary key and data in serialized object |
| 220 | * |
| 221 | * @return array |
| 222 | */ |
| 223 | public function __sleep() |
| 224 | { |
| 225 | return array('_tableClass', '_primary', '_data', '_cleanData', '_readOnly', '_modifiedFields'); |
| 226 | } |
| 227 | /** |
| 228 | * Setup to do on wakeup. |
| 229 | * A de-serialized Row should not be assumed to have access to a live |
| 230 | * database connection, so set _connected = false. |
| 231 | * |
| 232 | * @return void |
| 233 | */ |
| 234 | public function __wakeup() |
| 235 | { |
| 236 | $this->_connected = \false; |
| 237 | } |
| 238 | /** |
| 239 | * Proxy to __isset |
| 240 | * Required by the ArrayAccess implementation |
| 241 | * |
| 242 | * @param string $offset |
| 243 | * @return boolean |
| 244 | */ |
| 245 | public function offsetExists($offset) |
| 246 | { |
| 247 | return $this->__isset($offset); |
| 248 | } |
| 249 | /** |
| 250 | * Proxy to __get |
| 251 | * Required by the ArrayAccess implementation |
| 252 | * |
| 253 | * @param string $offset |
| 254 | * @return string |
| 255 | */ |
| 256 | public function offsetGet($offset) |
| 257 | { |
| 258 | return $this->__get($offset); |
| 259 | } |
| 260 | /** |
| 261 | * Proxy to __set |
| 262 | * Required by the ArrayAccess implementation |
| 263 | * |
| 264 | * @param string $offset |
| 265 | * @param mixed $value |
| 266 | */ |
| 267 | public function offsetSet($offset, $value) |
| 268 | { |
| 269 | $this->__set($offset, $value); |
| 270 | } |
| 271 | /** |
| 272 | * Proxy to __unset |
| 273 | * Required by the ArrayAccess implementation |
| 274 | * |
| 275 | * @param string $offset |
| 276 | */ |
| 277 | public function offsetUnset($offset) |
| 278 | { |
| 279 | return $this->__unset($offset); |
| 280 | } |
| 281 | /** |
| 282 | * Initialize object |
| 283 | * |
| 284 | * Called from {@link __construct()} as final step of object instantiation. |
| 285 | * |
| 286 | * @return void |
| 287 | */ |
| 288 | public function init() |
| 289 | { |
| 290 | } |
| 291 | /** |
| 292 | * Returns the table object, or null if this is disconnected row |
| 293 | * |
| 294 | * @return Zend_Db_Table_Abstract|null |
| 295 | */ |
| 296 | public function getTable() |
| 297 | { |
| 298 | return $this->_table; |
| 299 | } |
| 300 | /** |
| 301 | * Set the table object, to re-establish a live connection |
| 302 | * to the database for a Row that has been de-serialized. |
| 303 | * |
| 304 | * @param Zend_Db_Table_Abstract $table |
| 305 | * @return boolean |
| 306 | * @throws Zend_Db_Table_Row_Exception |
| 307 | */ |
| 308 | public function setTable(?\Zend_Db_Table_Abstract $table = null) |
| 309 | { |
| 310 | if ($table == null) { |
| 311 | $this->_table = null; |
| 312 | $this->_connected = \false; |
| 313 | return \false; |
| 314 | } |
| 315 | $tableClass = \get_class($table); |
| 316 | if (!$table instanceof $this->_tableClass) { |
| 317 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 318 | throw new \Zend_Db_Table_Row_Exception("The specified Table is of class {$tableClass}, expecting class to be instance of {$this->_tableClass}"); |
| 319 | } |
| 320 | $this->_table = $table; |
| 321 | $this->_tableClass = $tableClass; |
| 322 | $info = $this->_table->info(); |
| 323 | if ($info['cols'] != \array_keys($this->_data)) { |
| 324 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 325 | throw new \Zend_Db_Table_Row_Exception('The specified Table does not have the same columns as the Row'); |
| 326 | } |
| 327 | if (!\array_intersect((array) $this->_primary, $info['primary']) == (array) $this->_primary) { |
| 328 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 329 | throw new \Zend_Db_Table_Row_Exception("The specified Table '{$tableClass}' does not have the same primary key as the Row"); |
| 330 | } |
| 331 | $this->_connected = \true; |
| 332 | return \true; |
| 333 | } |
| 334 | /** |
| 335 | * Query the class name of the Table object for which this |
| 336 | * Row was created. |
| 337 | * |
| 338 | * @return string |
| 339 | */ |
| 340 | public function getTableClass() |
| 341 | { |
| 342 | return $this->_tableClass; |
| 343 | } |
| 344 | /** |
| 345 | * Test the connected status of the row. |
| 346 | * |
| 347 | * @return boolean |
| 348 | */ |
| 349 | public function isConnected() |
| 350 | { |
| 351 | return $this->_connected; |
| 352 | } |
| 353 | /** |
| 354 | * Test the read-only status of the row. |
| 355 | * |
| 356 | * @return boolean |
| 357 | */ |
| 358 | public function isReadOnly() |
| 359 | { |
| 360 | return $this->_readOnly; |
| 361 | } |
| 362 | /** |
| 363 | * Set the read-only status of the row. |
| 364 | * |
| 365 | * @param boolean $flag |
| 366 | * @return boolean |
| 367 | */ |
| 368 | public function setReadOnly($flag) |
| 369 | { |
| 370 | $this->_readOnly = (bool) $flag; |
| 371 | } |
| 372 | /** |
| 373 | * Returns an instance of the parent table's Zend_Db_Table_Select object. |
| 374 | * |
| 375 | * @return Zend_Db_Table_Select |
| 376 | */ |
| 377 | public function select() |
| 378 | { |
| 379 | return $this->getTable()->select(); |
| 380 | } |
| 381 | /** |
| 382 | * Saves the properties to the database. |
| 383 | * |
| 384 | * This performs an intelligent insert/update, and reloads the |
| 385 | * properties with fresh data from the table on success. |
| 386 | * |
| 387 | * @return mixed The primary key value(s), as an associative array if the |
| 388 | * key is compound, or a scalar if the key is single-column. |
| 389 | */ |
| 390 | public function save() |
| 391 | { |
| 392 | /** |
| 393 | * If the _cleanData array is empty, |
| 394 | * this is an INSERT of a new row. |
| 395 | * Otherwise it is an UPDATE. |
| 396 | */ |
| 397 | if (empty($this->_cleanData)) { |
| 398 | return $this->_doInsert(); |
| 399 | } else { |
| 400 | return $this->_doUpdate(); |
| 401 | } |
| 402 | } |
| 403 | /** |
| 404 | * @return mixed The primary key value(s), as an associative array if the |
| 405 | * key is compound, or a scalar if the key is single-column. |
| 406 | */ |
| 407 | protected function _doInsert() |
| 408 | { |
| 409 | /** |
| 410 | * A read-only row cannot be saved. |
| 411 | */ |
| 412 | if ($this->_readOnly === \true) { |
| 413 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 414 | throw new \Zend_Db_Table_Row_Exception('This row has been marked read-only'); |
| 415 | } |
| 416 | /** |
| 417 | * Run pre-INSERT logic |
| 418 | */ |
| 419 | $this->_insert(); |
| 420 | /** |
| 421 | * Execute the INSERT (this may throw an exception) |
| 422 | */ |
| 423 | $data = \array_intersect_key($this->_data, $this->_modifiedFields); |
| 424 | $primaryKey = $this->_getTable()->insert($data); |
| 425 | /** |
| 426 | * Normalize the result to an array indexed by primary key column(s). |
| 427 | * The table insert() method may return a scalar. |
| 428 | */ |
| 429 | if (\is_array($primaryKey)) { |
| 430 | $newPrimaryKey = $primaryKey; |
| 431 | } else { |
| 432 | //ZF-6167 Use tempPrimaryKey temporary to avoid that zend encoding fails. |
| 433 | $tempPrimaryKey = (array) $this->_primary; |
| 434 | $newPrimaryKey = array(\current($tempPrimaryKey) => $primaryKey); |
| 435 | } |
| 436 | /** |
| 437 | * Save the new primary key value in _data. The primary key may have |
| 438 | * been generated by a sequence or auto-increment mechanism, and this |
| 439 | * merge should be done before the _postInsert() method is run, so the |
| 440 | * new values are available for logging, etc. |
| 441 | */ |
| 442 | $this->_data = \array_merge($this->_data, $newPrimaryKey); |
| 443 | /** |
| 444 | * Run post-INSERT logic |
| 445 | */ |
| 446 | $this->_postInsert(); |
| 447 | /** |
| 448 | * Update the _cleanData to reflect that the data has been inserted. |
| 449 | */ |
| 450 | $this->_refresh(); |
| 451 | return $primaryKey; |
| 452 | } |
| 453 | /** |
| 454 | * @return mixed The primary key value(s), as an associative array if the |
| 455 | * key is compound, or a scalar if the key is single-column. |
| 456 | */ |
| 457 | protected function _doUpdate() |
| 458 | { |
| 459 | /** |
| 460 | * A read-only row cannot be saved. |
| 461 | */ |
| 462 | if ($this->_readOnly === \true) { |
| 463 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 464 | throw new \Zend_Db_Table_Row_Exception('This row has been marked read-only'); |
| 465 | } |
| 466 | /** |
| 467 | * Get expressions for a WHERE clause |
| 468 | * based on the primary key value(s). |
| 469 | */ |
| 470 | $where = $this->_getWhereQuery(\false); |
| 471 | /** |
| 472 | * Run pre-UPDATE logic |
| 473 | */ |
| 474 | $this->_update(); |
| 475 | /** |
| 476 | * Compare the data to the modified fields array to discover |
| 477 | * which columns have been changed. |
| 478 | */ |
| 479 | $diffData = \array_intersect_key($this->_data, $this->_modifiedFields); |
| 480 | /** |
| 481 | * Were any of the changed columns part of the primary key? |
| 482 | */ |
| 483 | $pkDiffData = \array_intersect_key($diffData, \array_flip((array) $this->_primary)); |
| 484 | /** |
| 485 | * Execute cascading updates against dependent tables. |
| 486 | * Do this only if primary key value(s) were changed. |
| 487 | */ |
| 488 | if (\count($pkDiffData) > 0) { |
| 489 | $depTables = $this->_getTable()->getDependentTables(); |
| 490 | if (!empty($depTables)) { |
| 491 | $pkNew = $this->_getPrimaryKey(\true); |
| 492 | $pkOld = $this->_getPrimaryKey(\false); |
| 493 | foreach ($depTables as $tableClass) { |
| 494 | $t = $this->_getTableFromString($tableClass); |
| 495 | $t->_cascadeUpdate($this->getTableClass(), $pkOld, $pkNew); |
| 496 | } |
| 497 | } |
| 498 | } |
| 499 | /** |
| 500 | * Execute the UPDATE (this may throw an exception) |
| 501 | * Do this only if data values were changed. |
| 502 | * Use the $diffData variable, so the UPDATE statement |
| 503 | * includes SET terms only for data values that changed. |
| 504 | */ |
| 505 | if (\count($diffData) > 0) { |
| 506 | $this->_getTable()->update($diffData, $where); |
| 507 | } |
| 508 | /** |
| 509 | * Run post-UPDATE logic. Do this before the _refresh() |
| 510 | * so the _postUpdate() function can tell the difference |
| 511 | * between changed data and clean (pre-changed) data. |
| 512 | */ |
| 513 | $this->_postUpdate(); |
| 514 | /** |
| 515 | * Refresh the data just in case triggers in the RDBMS changed |
| 516 | * any columns. Also this resets the _cleanData. |
| 517 | */ |
| 518 | $this->_refresh(); |
| 519 | /** |
| 520 | * Return the primary key value(s) as an array |
| 521 | * if the key is compound or a scalar if the key |
| 522 | * is a scalar. |
| 523 | */ |
| 524 | $primaryKey = $this->_getPrimaryKey(\true); |
| 525 | if (\count($primaryKey) == 1) { |
| 526 | return \current($primaryKey); |
| 527 | } |
| 528 | return $primaryKey; |
| 529 | } |
| 530 | /** |
| 531 | * Deletes existing rows. |
| 532 | * |
| 533 | * @return int The number of rows deleted. |
| 534 | */ |
| 535 | public function delete() |
| 536 | { |
| 537 | /** |
| 538 | * A read-only row cannot be deleted. |
| 539 | */ |
| 540 | if ($this->_readOnly === \true) { |
| 541 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 542 | throw new \Zend_Db_Table_Row_Exception('This row has been marked read-only'); |
| 543 | } |
| 544 | $where = $this->_getWhereQuery(); |
| 545 | /** |
| 546 | * Execute pre-DELETE logic |
| 547 | */ |
| 548 | $this->_delete(); |
| 549 | /** |
| 550 | * Execute cascading deletes against dependent tables |
| 551 | */ |
| 552 | $depTables = $this->_getTable()->getDependentTables(); |
| 553 | if (!empty($depTables)) { |
| 554 | $pk = $this->_getPrimaryKey(); |
| 555 | foreach ($depTables as $tableClass) { |
| 556 | $t = $this->_getTableFromString($tableClass); |
| 557 | $t->_cascadeDelete($this->getTableClass(), $pk); |
| 558 | } |
| 559 | } |
| 560 | /** |
| 561 | * Execute the DELETE (this may throw an exception) |
| 562 | */ |
| 563 | $result = $this->_getTable()->delete($where); |
| 564 | /** |
| 565 | * Execute post-DELETE logic |
| 566 | */ |
| 567 | $this->_postDelete(); |
| 568 | /** |
| 569 | * Reset all fields to null to indicate that the row is not there |
| 570 | */ |
| 571 | $this->_data = \array_combine(\array_keys($this->_data), \array_fill(0, \count($this->_data), null)); |
| 572 | return $result; |
| 573 | } |
| 574 | public function getIterator() |
| 575 | { |
| 576 | return new \ArrayIterator((array) $this->_data); |
| 577 | } |
| 578 | /** |
| 579 | * Returns the column/value data as an array. |
| 580 | * |
| 581 | * @return array |
| 582 | */ |
| 583 | public function toArray() |
| 584 | { |
| 585 | return (array) $this->_data; |
| 586 | } |
| 587 | /** |
| 588 | * Sets all data in the row from an array. |
| 589 | * |
| 590 | * @param array $data |
| 591 | * @return Zend_Db_Table_Row_Abstract Provides a fluent interface |
| 592 | */ |
| 593 | public function setFromArray(array $data) |
| 594 | { |
| 595 | $data = \array_intersect_key($data, $this->_data); |
| 596 | foreach ($data as $columnName => $value) { |
| 597 | $this->__set($columnName, $value); |
| 598 | } |
| 599 | return $this; |
| 600 | } |
| 601 | /** |
| 602 | * Refreshes properties from the database. |
| 603 | * |
| 604 | * @return void |
| 605 | */ |
| 606 | public function refresh() |
| 607 | { |
| 608 | return $this->_refresh(); |
| 609 | } |
| 610 | /** |
| 611 | * Retrieves an instance of the parent table. |
| 612 | * |
| 613 | * @return Zend_Db_Table_Abstract |
| 614 | */ |
| 615 | protected function _getTable() |
| 616 | { |
| 617 | if (!$this->_connected) { |
| 618 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 619 | throw new \Zend_Db_Table_Row_Exception('Cannot save a Row unless it is connected'); |
| 620 | } |
| 621 | return $this->_table; |
| 622 | } |
| 623 | /** |
| 624 | * Retrieves an associative array of primary keys. |
| 625 | * |
| 626 | * @param bool $useDirty |
| 627 | * @return array |
| 628 | */ |
| 629 | protected function _getPrimaryKey($useDirty = \true) |
| 630 | { |
| 631 | if (!\is_array($this->_primary)) { |
| 632 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 633 | throw new \Zend_Db_Table_Row_Exception("The primary key must be set as an array"); |
| 634 | } |
| 635 | $primary = \array_flip($this->_primary); |
| 636 | if ($useDirty) { |
| 637 | $array = \array_intersect_key($this->_data, $primary); |
| 638 | } else { |
| 639 | $array = \array_intersect_key($this->_cleanData, $primary); |
| 640 | } |
| 641 | if (\count($primary) != \count($array)) { |
| 642 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 643 | throw new \Zend_Db_Table_Row_Exception("The specified Table '{$this->_tableClass}' does not have the same primary key as the Row"); |
| 644 | } |
| 645 | return $array; |
| 646 | } |
| 647 | /** |
| 648 | * Constructs where statement for retrieving row(s). |
| 649 | * |
| 650 | * @param bool $useDirty |
| 651 | * @return array |
| 652 | */ |
| 653 | protected function _getWhereQuery($useDirty = \true) |
| 654 | { |
| 655 | $where = array(); |
| 656 | $db = $this->_getTable()->getAdapter(); |
| 657 | $primaryKey = $this->_getPrimaryKey($useDirty); |
| 658 | $info = $this->_getTable()->info(); |
| 659 | $metadata = $info[\Zend_Db_Table_Abstract::METADATA]; |
| 660 | // retrieve recently updated row using primary keys |
| 661 | $where = array(); |
| 662 | foreach ($primaryKey as $column => $value) { |
| 663 | $tableName = $db->quoteIdentifier($info[\Zend_Db_Table_Abstract::NAME], \true); |
| 664 | $type = $metadata[$column]['DATA_TYPE']; |
| 665 | $columnName = $db->quoteIdentifier($column, \true); |
| 666 | $where[] = $db->quoteInto("{$tableName}.{$columnName} = ?", $value, $type); |
| 667 | } |
| 668 | return $where; |
| 669 | } |
| 670 | /** |
| 671 | * Refreshes properties from the database. |
| 672 | * |
| 673 | * @return void |
| 674 | */ |
| 675 | protected function _refresh() |
| 676 | { |
| 677 | $where = $this->_getWhereQuery(); |
| 678 | $row = $this->_getTable()->fetchRow($where); |
| 679 | if (null === $row) { |
| 680 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 681 | throw new \Zend_Db_Table_Row_Exception('Cannot refresh row as parent is missing'); |
| 682 | } |
| 683 | $this->_data = $row->toArray(); |
| 684 | $this->_cleanData = $this->_data; |
| 685 | $this->_modifiedFields = array(); |
| 686 | } |
| 687 | /** |
| 688 | * Allows pre-insert logic to be applied to row. |
| 689 | * Subclasses may override this method. |
| 690 | * |
| 691 | * @return void |
| 692 | */ |
| 693 | protected function _insert() |
| 694 | { |
| 695 | } |
| 696 | /** |
| 697 | * Allows post-insert logic to be applied to row. |
| 698 | * Subclasses may override this method. |
| 699 | * |
| 700 | * @return void |
| 701 | */ |
| 702 | protected function _postInsert() |
| 703 | { |
| 704 | } |
| 705 | /** |
| 706 | * Allows pre-update logic to be applied to row. |
| 707 | * Subclasses may override this method. |
| 708 | * |
| 709 | * @return void |
| 710 | */ |
| 711 | protected function _update() |
| 712 | { |
| 713 | } |
| 714 | /** |
| 715 | * Allows post-update logic to be applied to row. |
| 716 | * Subclasses may override this method. |
| 717 | * |
| 718 | * @return void |
| 719 | */ |
| 720 | protected function _postUpdate() |
| 721 | { |
| 722 | } |
| 723 | /** |
| 724 | * Allows pre-delete logic to be applied to row. |
| 725 | * Subclasses may override this method. |
| 726 | * |
| 727 | * @return void |
| 728 | */ |
| 729 | protected function _delete() |
| 730 | { |
| 731 | } |
| 732 | /** |
| 733 | * Allows post-delete logic to be applied to row. |
| 734 | * Subclasses may override this method. |
| 735 | * |
| 736 | * @return void |
| 737 | */ |
| 738 | protected function _postDelete() |
| 739 | { |
| 740 | } |
| 741 | /** |
| 742 | * Prepares a table reference for lookup. |
| 743 | * |
| 744 | * Ensures all reference keys are set and properly formatted. |
| 745 | * |
| 746 | * @param Zend_Db_Table_Abstract $dependentTable |
| 747 | * @param Zend_Db_Table_Abstract $parentTable |
| 748 | * @param string $ruleKey |
| 749 | * @return array |
| 750 | */ |
| 751 | protected function _prepareReference(\Zend_Db_Table_Abstract $dependentTable, \Zend_Db_Table_Abstract $parentTable, $ruleKey) |
| 752 | { |
| 753 | $parentTableName = \get_class($parentTable) === 'Zend_Db_Table' ? $parentTable->getDefinitionConfigName() : \get_class($parentTable); |
| 754 | $map = $dependentTable->getReference($parentTableName, $ruleKey); |
| 755 | if (!isset($map[\Zend_Db_Table_Abstract::REF_COLUMNS])) { |
| 756 | $parentInfo = $parentTable->info(); |
| 757 | $map[\Zend_Db_Table_Abstract::REF_COLUMNS] = \array_values((array) $parentInfo['primary']); |
| 758 | } |
| 759 | $map[\Zend_Db_Table_Abstract::COLUMNS] = (array) $map[\Zend_Db_Table_Abstract::COLUMNS]; |
| 760 | $map[\Zend_Db_Table_Abstract::REF_COLUMNS] = (array) $map[\Zend_Db_Table_Abstract::REF_COLUMNS]; |
| 761 | return $map; |
| 762 | } |
| 763 | /** |
| 764 | * Query a dependent table to retrieve rows matching the current row. |
| 765 | * |
| 766 | * @param string|Zend_Db_Table_Abstract $dependentTable |
| 767 | * @param string OPTIONAL $ruleKey |
| 768 | * @param Zend_Db_Table_Select OPTIONAL $select |
| 769 | * @return Zend_Db_Table_Rowset_Abstract Query result from $dependentTable |
| 770 | * @throws Zend_Db_Table_Row_Exception If $dependentTable is not a table or is not loadable. |
| 771 | */ |
| 772 | public function findDependentRowset($dependentTable, $ruleKey = null, ?\Zend_Db_Table_Select $select = null) |
| 773 | { |
| 774 | $db = $this->_getTable()->getAdapter(); |
| 775 | if (\is_string($dependentTable)) { |
| 776 | $dependentTable = $this->_getTableFromString($dependentTable); |
| 777 | } |
| 778 | if (!$dependentTable instanceof \Zend_Db_Table_Abstract) { |
| 779 | $type = \gettype($dependentTable); |
| 780 | if ($type == 'object') { |
| 781 | $type = \get_class($dependentTable); |
| 782 | } |
| 783 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 784 | throw new \Zend_Db_Table_Row_Exception("Dependent table must be a Zend_Db_Table_Abstract, but it is {$type}"); |
| 785 | } |
| 786 | // even if we are interacting between a table defined in a class and a |
| 787 | // table via extension, ensure to persist the definition |
| 788 | if (($tableDefinition = $this->_table->getDefinition()) !== null && $dependentTable->getDefinition() == null) { |
| 789 | $dependentTable->setOptions(array(\Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); |
| 790 | } |
| 791 | if ($select === null) { |
| 792 | $select = $dependentTable->select(); |
| 793 | } else { |
| 794 | $select->setTable($dependentTable); |
| 795 | } |
| 796 | $map = $this->_prepareReference($dependentTable, $this->_getTable(), $ruleKey); |
| 797 | for ($i = 0; $i < \count($map[\Zend_Db_Table_Abstract::COLUMNS]); ++$i) { |
| 798 | $parentColumnName = $db->foldCase($map[\Zend_Db_Table_Abstract::REF_COLUMNS][$i]); |
| 799 | $value = $this->_data[$parentColumnName]; |
| 800 | // Use adapter from dependent table to ensure correct query construction |
| 801 | $dependentDb = $dependentTable->getAdapter(); |
| 802 | $dependentColumnName = $dependentDb->foldCase($map[\Zend_Db_Table_Abstract::COLUMNS][$i]); |
| 803 | $dependentColumn = $dependentDb->quoteIdentifier($dependentColumnName, \true); |
| 804 | $dependentInfo = $dependentTable->info(); |
| 805 | $type = $dependentInfo[\Zend_Db_Table_Abstract::METADATA][$dependentColumnName]['DATA_TYPE']; |
| 806 | $select->where("{$dependentColumn} = ?", $value, $type); |
| 807 | } |
| 808 | return $dependentTable->fetchAll($select); |
| 809 | } |
| 810 | /** |
| 811 | * Query a parent table to retrieve the single row matching the current row. |
| 812 | * |
| 813 | * @param string|Zend_Db_Table_Abstract $parentTable |
| 814 | * @param string OPTIONAL $ruleKey |
| 815 | * @param Zend_Db_Table_Select OPTIONAL $select |
| 816 | * @return Zend_Db_Table_Row_Abstract Query result from $parentTable |
| 817 | * @throws Zend_Db_Table_Row_Exception If $parentTable is not a table or is not loadable. |
| 818 | */ |
| 819 | public function findParentRow($parentTable, $ruleKey = null, \Zend_Db_Table_Select $select = null) |
| 820 | { |
| 821 | $db = $this->_getTable()->getAdapter(); |
| 822 | if (\is_string($parentTable)) { |
| 823 | $parentTable = $this->_getTableFromString($parentTable); |
| 824 | } |
| 825 | if (!$parentTable instanceof \Zend_Db_Table_Abstract) { |
| 826 | $type = \gettype($parentTable); |
| 827 | if ($type == 'object') { |
| 828 | $type = \get_class($parentTable); |
| 829 | } |
| 830 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 831 | throw new \Zend_Db_Table_Row_Exception("Parent table must be a Zend_Db_Table_Abstract, but it is {$type}"); |
| 832 | } |
| 833 | // even if we are interacting between a table defined in a class and a |
| 834 | // table via extension, ensure to persist the definition |
| 835 | if (($tableDefinition = $this->_table->getDefinition()) !== null && $parentTable->getDefinition() == null) { |
| 836 | $parentTable->setOptions(array(\Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); |
| 837 | } |
| 838 | if ($select === null) { |
| 839 | $select = $parentTable->select(); |
| 840 | } else { |
| 841 | $select->setTable($parentTable); |
| 842 | } |
| 843 | $map = $this->_prepareReference($this->_getTable(), $parentTable, $ruleKey); |
| 844 | // iterate the map, creating the proper wheres |
| 845 | for ($i = 0; $i < \count($map[\Zend_Db_Table_Abstract::COLUMNS]); ++$i) { |
| 846 | $dependentColumnName = $db->foldCase($map[\Zend_Db_Table_Abstract::COLUMNS][$i]); |
| 847 | $value = $this->_data[$dependentColumnName]; |
| 848 | // Use adapter from parent table to ensure correct query construction |
| 849 | $parentDb = $parentTable->getAdapter(); |
| 850 | $parentColumnName = $parentDb->foldCase($map[\Zend_Db_Table_Abstract::REF_COLUMNS][$i]); |
| 851 | $parentColumn = $parentDb->quoteIdentifier($parentColumnName, \true); |
| 852 | $parentInfo = $parentTable->info(); |
| 853 | // determine where part |
| 854 | $type = $parentInfo[\Zend_Db_Table_Abstract::METADATA][$parentColumnName]['DATA_TYPE']; |
| 855 | $nullable = $parentInfo[\Zend_Db_Table_Abstract::METADATA][$parentColumnName]['NULLABLE']; |
| 856 | if ($value === null && $nullable == \true) { |
| 857 | $select->where("{$parentColumn} IS NULL"); |
| 858 | } elseif ($value === null && $nullable == \false) { |
| 859 | return null; |
| 860 | } else { |
| 861 | $select->where("{$parentColumn} = ?", $value, $type); |
| 862 | } |
| 863 | } |
| 864 | return $parentTable->fetchRow($select); |
| 865 | } |
| 866 | /** |
| 867 | * @param string|Zend_Db_Table_Abstract $matchTable |
| 868 | * @param string|Zend_Db_Table_Abstract $intersectionTable |
| 869 | * @param string OPTIONAL $callerRefRule |
| 870 | * @param string OPTIONAL $matchRefRule |
| 871 | * @param Zend_Db_Table_Select OPTIONAL $select |
| 872 | * @return Zend_Db_Table_Rowset_Abstract Query result from $matchTable |
| 873 | * @throws Zend_Db_Table_Row_Exception If $matchTable or $intersectionTable is not a table class or is not loadable. |
| 874 | */ |
| 875 | public function findManyToManyRowset($matchTable, $intersectionTable, $callerRefRule = null, $matchRefRule = null, ?\Zend_Db_Table_Select $select = null) |
| 876 | { |
| 877 | $db = $this->_getTable()->getAdapter(); |
| 878 | if (\is_string($intersectionTable)) { |
| 879 | $intersectionTable = $this->_getTableFromString($intersectionTable); |
| 880 | } |
| 881 | if (!$intersectionTable instanceof \Zend_Db_Table_Abstract) { |
| 882 | $type = \gettype($intersectionTable); |
| 883 | if ($type == 'object') { |
| 884 | $type = \get_class($intersectionTable); |
| 885 | } |
| 886 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 887 | throw new \Zend_Db_Table_Row_Exception("Intersection table must be a Zend_Db_Table_Abstract, but it is {$type}"); |
| 888 | } |
| 889 | // even if we are interacting between a table defined in a class and a |
| 890 | // table via extension, ensure to persist the definition |
| 891 | if (($tableDefinition = $this->_table->getDefinition()) !== null && $intersectionTable->getDefinition() == null) { |
| 892 | $intersectionTable->setOptions(array(\Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); |
| 893 | } |
| 894 | if (\is_string($matchTable)) { |
| 895 | $matchTable = $this->_getTableFromString($matchTable); |
| 896 | } |
| 897 | if (!$matchTable instanceof \Zend_Db_Table_Abstract) { |
| 898 | $type = \gettype($matchTable); |
| 899 | if ($type == 'object') { |
| 900 | $type = \get_class($matchTable); |
| 901 | } |
| 902 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 903 | throw new \Zend_Db_Table_Row_Exception("Match table must be a Zend_Db_Table_Abstract, but it is {$type}"); |
| 904 | } |
| 905 | // even if we are interacting between a table defined in a class and a |
| 906 | // table via extension, ensure to persist the definition |
| 907 | if (($tableDefinition = $this->_table->getDefinition()) !== null && $matchTable->getDefinition() == null) { |
| 908 | $matchTable->setOptions(array(\Zend_Db_Table_Abstract::DEFINITION => $tableDefinition)); |
| 909 | } |
| 910 | if ($select === null) { |
| 911 | $select = $matchTable->select(); |
| 912 | } else { |
| 913 | $select->setTable($matchTable); |
| 914 | } |
| 915 | // Use adapter from intersection table to ensure correct query construction |
| 916 | $interInfo = $intersectionTable->info(); |
| 917 | $interDb = $intersectionTable->getAdapter(); |
| 918 | $interName = $interInfo['name']; |
| 919 | $interSchema = isset($interInfo['schema']) ? $interInfo['schema'] : null; |
| 920 | $matchInfo = $matchTable->info(); |
| 921 | $matchName = $matchInfo['name']; |
| 922 | $matchSchema = isset($matchInfo['schema']) ? $matchInfo['schema'] : null; |
| 923 | $matchMap = $this->_prepareReference($intersectionTable, $matchTable, $matchRefRule); |
| 924 | for ($i = 0; $i < \count($matchMap[\Zend_Db_Table_Abstract::COLUMNS]); ++$i) { |
| 925 | $interCol = $interDb->quoteIdentifier('i' . '.' . $matchMap[\Zend_Db_Table_Abstract::COLUMNS][$i], \true); |
| 926 | $matchCol = $interDb->quoteIdentifier('m' . '.' . $matchMap[\Zend_Db_Table_Abstract::REF_COLUMNS][$i], \true); |
| 927 | $joinCond[] = "{$interCol} = {$matchCol}"; |
| 928 | } |
| 929 | $joinCond = \implode(' AND ', $joinCond); |
| 930 | $select->from(array('i' => $interName), array(), $interSchema)->joinInner(array('m' => $matchName), $joinCond, \Zend_Db_Select::SQL_WILDCARD, $matchSchema)->setIntegrityCheck(\false); |
| 931 | $callerMap = $this->_prepareReference($intersectionTable, $this->_getTable(), $callerRefRule); |
| 932 | for ($i = 0; $i < \count($callerMap[\Zend_Db_Table_Abstract::COLUMNS]); ++$i) { |
| 933 | $callerColumnName = $db->foldCase($callerMap[\Zend_Db_Table_Abstract::REF_COLUMNS][$i]); |
| 934 | $value = $this->_data[$callerColumnName]; |
| 935 | $interColumnName = $interDb->foldCase($callerMap[\Zend_Db_Table_Abstract::COLUMNS][$i]); |
| 936 | $interCol = $interDb->quoteIdentifier("i.{$interColumnName}", \true); |
| 937 | $interInfo = $intersectionTable->info(); |
| 938 | $type = $interInfo[\Zend_Db_Table_Abstract::METADATA][$interColumnName]['DATA_TYPE']; |
| 939 | $select->where($interDb->quoteInto("{$interCol} = ?", $value, $type)); |
| 940 | } |
| 941 | $stmt = $select->query(); |
| 942 | $config = array('table' => $matchTable, 'data' => $stmt->fetchAll(\Zend_Db::FETCH_ASSOC), 'rowClass' => $matchTable->getRowClass(), 'readOnly' => \false, 'stored' => \true); |
| 943 | $rowsetClass = $matchTable->getRowsetClass(); |
| 944 | if (!\class_exists($rowsetClass)) { |
| 945 | try { |
| 946 | // require_once 'Zend/Loader.php'; |
| 947 | \Zend_Loader::loadClass($rowsetClass); |
| 948 | } catch (\Zend_Exception $e) { |
| 949 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 950 | throw new \Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e); |
| 951 | } |
| 952 | } |
| 953 | $rowset = new $rowsetClass($config); |
| 954 | return $rowset; |
| 955 | } |
| 956 | /** |
| 957 | * Turn magic function calls into non-magic function calls |
| 958 | * to the above methods. |
| 959 | * |
| 960 | * @param string $method |
| 961 | * @param array $args OPTIONAL Zend_Db_Table_Select query modifier |
| 962 | * @return Zend_Db_Table_Row_Abstract|Zend_Db_Table_Rowset_Abstract |
| 963 | * @throws Zend_Db_Table_Row_Exception If an invalid method is called. |
| 964 | */ |
| 965 | public function __call($method, array $args) |
| 966 | { |
| 967 | $matches = array(); |
| 968 | if (\count($args) && $args[0] instanceof \Zend_Db_Table_Select) { |
| 969 | $select = $args[0]; |
| 970 | } else { |
| 971 | $select = null; |
| 972 | } |
| 973 | /** |
| 974 | * Recognize methods for Has-Many cases: |
| 975 | * findParent<Class>() |
| 976 | * findParent<Class>By<Rule>() |
| 977 | * Use the non-greedy pattern repeat modifier e.g. \w+? |
| 978 | */ |
| 979 | if (\preg_match('/^findParent(\\w+?)(?:By(\\w+))?$/', $method, $matches)) { |
| 980 | $class = $matches[1]; |
| 981 | $ruleKey1 = isset($matches[2]) ? $matches[2] : null; |
| 982 | return $this->findParentRow($class, $ruleKey1, $select); |
| 983 | } |
| 984 | /** |
| 985 | * Recognize methods for Many-to-Many cases: |
| 986 | * find<Class1>Via<Class2>() |
| 987 | * find<Class1>Via<Class2>By<Rule>() |
| 988 | * find<Class1>Via<Class2>By<Rule1>And<Rule2>() |
| 989 | * Use the non-greedy pattern repeat modifier e.g. \w+? |
| 990 | */ |
| 991 | if (\preg_match('/^find(\\w+?)Via(\\w+?)(?:By(\\w+?)(?:And(\\w+))?)?$/', $method, $matches)) { |
| 992 | $class = $matches[1]; |
| 993 | $viaClass = $matches[2]; |
| 994 | $ruleKey1 = isset($matches[3]) ? $matches[3] : null; |
| 995 | $ruleKey2 = isset($matches[4]) ? $matches[4] : null; |
| 996 | return $this->findManyToManyRowset($class, $viaClass, $ruleKey1, $ruleKey2, $select); |
| 997 | } |
| 998 | /** |
| 999 | * Recognize methods for Belongs-To cases: |
| 1000 | * find<Class>() |
| 1001 | * find<Class>By<Rule>() |
| 1002 | * Use the non-greedy pattern repeat modifier e.g. \w+? |
| 1003 | */ |
| 1004 | if (\preg_match('/^find(\\w+?)(?:By(\\w+))?$/', $method, $matches)) { |
| 1005 | $class = $matches[1]; |
| 1006 | $ruleKey1 = isset($matches[2]) ? $matches[2] : null; |
| 1007 | return $this->findDependentRowset($class, $ruleKey1, $select); |
| 1008 | } |
| 1009 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 1010 | throw new \Zend_Db_Table_Row_Exception("Unrecognized method '{$method}()'"); |
| 1011 | } |
| 1012 | /** |
| 1013 | * _getTableFromString |
| 1014 | * |
| 1015 | * @param string $tableName |
| 1016 | * @return Zend_Db_Table_Abstract |
| 1017 | */ |
| 1018 | protected function _getTableFromString($tableName) |
| 1019 | { |
| 1020 | if ($this->_table instanceof \Zend_Db_Table_Abstract) { |
| 1021 | $tableDefinition = $this->_table->getDefinition(); |
| 1022 | if ($tableDefinition !== null && $tableDefinition->hasTableConfig($tableName)) { |
| 1023 | return new \Zend_Db_Table($tableName, $tableDefinition); |
| 1024 | } |
| 1025 | } |
| 1026 | // assume the tableName is the class name |
| 1027 | if (!\class_exists($tableName)) { |
| 1028 | try { |
| 1029 | // require_once 'Zend/Loader.php'; |
| 1030 | \Zend_Loader::loadClass($tableName); |
| 1031 | } catch (\Zend_Exception $e) { |
| 1032 | // require_once 'Zend/Db/Table/Row/Exception.php'; |
| 1033 | throw new \Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e); |
| 1034 | } |
| 1035 | } |
| 1036 | $options = array(); |
| 1037 | if ($table = $this->_getTable()) { |
| 1038 | $options['db'] = $table->getAdapter(); |
| 1039 | } |
| 1040 | if (isset($tableDefinition) && $tableDefinition !== null) { |
| 1041 | $options[\Zend_Db_Table_Abstract::DEFINITION] = $tableDefinition; |
| 1042 | } |
| 1043 | return new $tableName($options); |
| 1044 | } |
| 1045 | } |
| 1046 | } |
| 1047 |