1<?php
2/**
3 * Zend Framework
4 *
5 * LICENSE
6 *
7 * This source file is subject to the new BSD license that is bundled
8 * with this package in the file LICENSE.txt.
9 * It is also available through the world-wide-web at this URL:
10 * http://framework.zend.com/license/new-bsd
11 * If you did not receive a copy of the license and are unable to
12 * obtain it through the world-wide-web, please send an email
13 * to license@zend.com so we can send you a copy immediately.
14 *
15 * @category   Zend
16 * @package    Zend_Db
17 * @subpackage Table
18 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
19 * @license    http://framework.zend.com/license/new-bsd     New BSD License
20 * @version    $Id$
21 */
22
23/**
24 * @category   Zend
25 * @package    Zend_Db
26 * @subpackage Table
27 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
28 * @license    http://framework.zend.com/license/new-bsd     New BSD License
29 */
30abstract class Zend_Db_Table_Rowset_Abstract implements SeekableIterator, Countable, ArrayAccess
31{
32    /**
33     * The original data for each row.
34     *
35     * @var array
36     */
37    protected $_data = array();
38
39    /**
40     * Zend_Db_Table_Abstract object.
41     *
42     * @var Zend_Db_Table_Abstract
43     */
44    protected $_table;
45
46    /**
47     * Connected is true if we have a reference to a live
48     * Zend_Db_Table_Abstract object.
49     * This is false after the Rowset has been deserialized.
50     *
51     * @var boolean
52     */
53    protected $_connected = true;
54
55    /**
56     * Zend_Db_Table_Abstract class name.
57     *
58     * @var string
59     */
60    protected $_tableClass;
61
62    /**
63     * Zend_Db_Table_Row_Abstract class name.
64     *
65     * @var string
66     */
67    protected $_rowClass = 'Zend_Db_Table_Row';
68
69    /**
70     * Iterator pointer.
71     *
72     * @var integer
73     */
74    protected $_pointer = 0;
75
76    /**
77     * How many data rows there are.
78     *
79     * @var integer
80     */
81    protected $_count;
82
83    /**
84     * Collection of instantiated Zend_Db_Table_Row objects.
85     *
86     * @var array
87     */
88    protected $_rows = array();
89
90    /**
91     * @var boolean
92     */
93    protected $_stored = false;
94
95    /**
96     * @var boolean
97     */
98    protected $_readOnly = false;
99
100    /**
101     * Constructor.
102     *
103     * @param array $config
104     */
105    public function __construct(array $config)
106    {
107        if (isset($config['table'])) {
108            $this->_table      = $config['table'];
109            $this->_tableClass = get_class($this->_table);
110        }
111        if (isset($config['rowClass'])) {
112            $this->_rowClass   = $config['rowClass'];
113        }
114        if (!class_exists($this->_rowClass)) {
115            Zend_Loader::loadClass($this->_rowClass);
116        }
117        if (isset($config['data'])) {
118            $this->_data       = $config['data'];
119        }
120        if (isset($config['readOnly'])) {
121            $this->_readOnly   = $config['readOnly'];
122        }
123        if (isset($config['stored'])) {
124            $this->_stored     = $config['stored'];
125        }
126
127        // set the count of rows
128        $this->_count = count($this->_data);
129
130        $this->init();
131    }
132
133    /**
134     * Store data, class names, and state in serialized object
135     *
136     * @return array
137     */
138    public function __sleep()
139    {
140        return array('_data', '_tableClass', '_rowClass', '_pointer', '_count', '_rows', '_stored',
141                     '_readOnly');
142    }
143
144    /**
145     * Setup to do on wakeup.
146     * A de-serialized Rowset should not be assumed to have access to a live
147     * database connection, so set _connected = false.
148     *
149     * @return void
150     */
151    public function __wakeup()
152    {
153        $this->_connected = false;
154    }
155
156    /**
157     * Initialize object
158     *
159     * Called from {@link __construct()} as final step of object instantiation.
160     *
161     * @return void
162     */
163    public function init()
164    {
165    }
166
167    /**
168     * Return the connected state of the rowset.
169     *
170     * @return boolean
171     */
172    public function isConnected()
173    {
174        return $this->_connected;
175    }
176
177    /**
178     * Returns the table object, or null if this is disconnected rowset
179     *
180     * @return Zend_Db_Table_Abstract
181     */
182    public function getTable()
183    {
184        return $this->_table;
185    }
186
187    /**
188     * Set the table object, to re-establish a live connection
189     * to the database for a Rowset that has been de-serialized.
190     *
191     * @param Zend_Db_Table_Abstract $table
192     * @return boolean
193     * @throws Zend_Db_Table_Row_Exception
194     */
195    public function setTable(Zend_Db_Table_Abstract $table)
196    {
197        $this->_table = $table;
198        $this->_connected = false;
199        // @todo This works only if we have iterated through
200        // the result set once to instantiate the rows.
201        foreach ($this as $row) {
202            $connected = $row->setTable($table);
203            if ($connected == true) {
204                $this->_connected = true;
205            }
206        }
207        $this->rewind();
208        return $this->_connected;
209    }
210
211    /**
212     * Query the class name of the Table object for which this
213     * Rowset was created.
214     *
215     * @return string
216     */
217    public function getTableClass()
218    {
219        return $this->_tableClass;
220    }
221
222    /**
223     * Rewind the Iterator to the first element.
224     * Similar to the reset() function for arrays in PHP.
225     * Required by interface Iterator.
226     *
227     * @return Zend_Db_Table_Rowset_Abstract Fluent interface.
228     */
229    public function rewind()
230    {
231        $this->_pointer = 0;
232        return $this;
233    }
234
235    /**
236     * Return the current element.
237     * Similar to the current() function for arrays in PHP
238     * Required by interface Iterator.
239     *
240     * @return Zend_Db_Table_Row_Abstract current element from the collection
241     */
242    public function current()
243    {
244        if ($this->valid() === false) {
245            return null;
246        }
247
248        // return the row object
249        return $this->_loadAndReturnRow($this->_pointer);
250    }
251
252    /**
253     * Return the identifying key of the current element.
254     * Similar to the key() function for arrays in PHP.
255     * Required by interface Iterator.
256     *
257     * @return int
258     */
259    public function key()
260    {
261        return $this->_pointer;
262    }
263
264    /**
265     * Move forward to next element.
266     * Similar to the next() function for arrays in PHP.
267     * Required by interface Iterator.
268     *
269     * @return void
270     */
271    public function next()
272    {
273        ++$this->_pointer;
274    }
275
276    /**
277     * Check if there is a current element after calls to rewind() or next().
278     * Used to check if we've iterated to the end of the collection.
279     * Required by interface Iterator.
280     *
281     * @return bool False if there's nothing more to iterate over
282     */
283    public function valid()
284    {
285        return $this->_pointer >= 0 && $this->_pointer < $this->_count;
286    }
287
288    /**
289     * Returns the number of elements in the collection.
290     *
291     * Implements Countable::count()
292     *
293     * @return int
294     */
295    public function count()
296    {
297        return $this->_count;
298    }
299
300    /**
301     * Take the Iterator to position $position
302     * Required by interface SeekableIterator.
303     *
304     * @param int $position the position to seek to
305     * @return Zend_Db_Table_Rowset_Abstract
306     * @throws Zend_Db_Table_Rowset_Exception
307     */
308    public function seek($position)
309    {
310        $position = (int) $position;
311        if ($position < 0 || $position >= $this->_count) {
312            throw new Zend_Db_Table_Rowset_Exception("Illegal index $position");
313        }
314        $this->_pointer = $position;
315        return $this;
316    }
317
318    /**
319     * Check if an offset exists
320     * Required by the ArrayAccess implementation
321     *
322     * @param string $offset
323     * @return boolean
324     */
325    public function offsetExists($offset)
326    {
327        return isset($this->_data[(int) $offset]);
328    }
329
330    /**
331     * Get the row for the given offset
332     * Required by the ArrayAccess implementation
333     *
334     * @param string $offset
335     * @return Zend_Db_Table_Row_Abstract
336     */
337    public function offsetGet($offset)
338    {
339        $offset = (int) $offset;
340        if ($offset < 0 || $offset >= $this->_count) {
341            throw new Zend_Db_Table_Rowset_Exception("Illegal index $offset");
342        }
343        $this->_pointer = $offset;
344
345        return $this->current();
346    }
347
348    /**
349     * Does nothing
350     * Required by the ArrayAccess implementation
351     *
352     * @param string $offset
353     * @param mixed $value
354     */
355    public function offsetSet($offset, $value)
356    {
357    }
358
359    /**
360     * Does nothing
361     * Required by the ArrayAccess implementation
362     *
363     * @param string $offset
364     */
365    public function offsetUnset($offset)
366    {
367    }
368
369    /**
370     * Returns a Zend_Db_Table_Row from a known position into the Iterator
371     *
372     * @param int $position the position of the row expected
373     * @param bool $seek wether or not seek the iterator to that position after
374     * @return Zend_Db_Table_Row
375     * @throws Zend_Db_Table_Rowset_Exception
376     */
377    public function getRow($position, $seek = false)
378    {
379        try {
380            $row = $this->_loadAndReturnRow($position);
381        } catch (Zend_Db_Table_Rowset_Exception $e) {
382            throw new Zend_Db_Table_Rowset_Exception('No row could be found at position ' . (int) $position, 0, $e);
383        }
384
385        if ($seek == true) {
386            $this->seek($position);
387        }
388
389        return $row;
390    }
391
392    /**
393     * Returns all data as an array.
394     *
395     * Updates the $_data property with current row object values.
396     *
397     * @return array
398     */
399    public function toArray()
400    {
401        // @todo This works only if we have iterated through
402        // the result set once to instantiate the rows.
403        foreach ($this->_rows as $i => $row) {
404            $this->_data[$i] = $row->toArray();
405        }
406        return $this->_data;
407    }
408
409    protected function _loadAndReturnRow($position)
410    {
411        if (!isset($this->_data[$position])) {
412            throw new Zend_Db_Table_Rowset_Exception("Data for provided position does not exist");
413        }
414
415        // do we already have a row object for this position?
416        if (empty($this->_rows[$position])) {
417            $this->_rows[$position] = new $this->_rowClass(
418                array(
419                    'table'    => $this->_table,
420                    'data'     => $this->_data[$position],
421                    'stored'   => $this->_stored,
422                    'readOnly' => $this->_readOnly
423                )
424            );
425
426            if ( $this->_table instanceof Zend_Db_Table_Abstract ) {
427                $info = $this->_table->info();
428
429                if ( $this->_rows[$position] instanceof Zend_Db_Table_Row_Abstract ) {
430                    if ($info['cols'] == array_keys($this->_data[$position])) {
431                        $this->_rows[$position]->setTable($this->getTable());
432                    }
433                }
434            } else {
435                $this->_rows[$position]->setTable(null);
436            }
437        }
438
439        // return the row object
440        return $this->_rows[$position];
441    }
442
443}
444