PluginProbe
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.7.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.7.0
5.13.0 5.12.1 5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 All 83 releases
matomo / app / libs / Zend / Db / Table / Row / Abstract.php
Abstract.php
1,047 lines 42.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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