summaryrefslogtreecommitdiff
path: root/src/traverseiterator.h
blob: 6f2fce1bfda4b9dbfbae595108f882d986db20d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/**
 *  TraverseIterator.h
 *
 *  When an object from PHP userspace implements its own iterator methods,
 *  and the Php::Value object is used to iterate over its properties, this
 *  TraversableIterator class is used to iterate over the properties
 *
 *  @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
 *  @copyright 2014 Copernica BV
 */

/**
 *  Set up namespace
 */
namespace Php {

/**
 *  Class definition
 */
class TraverseIterator : public IteratorImpl
{
public:
    /**
     *  Constructor
     *  @param  object
     *  @param  begin
     */
    TraverseIterator(zval *object, bool begin) : _object(object)
    {
        // leap out if this iterator starts at the end
        if (!begin) return;
        
        // we need the class entry
        auto *entry = zend_get_class_entry(object);
        
        // create the iterator
        _iter = entry->get_iterator(entry, object, false);
        
        // rewind the iterator
        _iter->funcs->rewind(_iter);
        
        // read the first key/value pair
        read();
    }
    
    /**
     *  Copy constructor
     *  @param  that
     */
    TraverseIterator(const TraverseIterator &that) : TraverseIterator(that._object, that._iter != nullptr)
    {
        // @todo    this is a broken implementation, the copy is at the start
        //          position, while we'd like to be at the same position
    }
    
    /**
     *  Destructor
     */
    virtual ~TraverseIterator()
    {
        // call the iterator destructor
        if (_iter) _iter->funcs->dtor(_iter);
    }

    /**
     *  Clone the object
     *  @return IteratorImpl*
     */
    virtual IteratorImpl *clone() override
    {
        return new TraverseIterator(*this);
    }

    /**
     *  Increment position (pre-increment)
     *  @return bool
     */
    virtual bool increment() override
    {
        // do we still have an iterator?
        if (!_iter) return false;
        
        // movw it forward
        _iter->funcs->move_forward(_iter);
        
        // and read current data
        read();
    }
    
    /**
     *  Decrement position (pre-decrement)
     *  @return bool
     */
    virtual bool decrement() override
    {
        // not possible with PHP iterators
        throw Exception("Impossible to iterate backwards");
    }

    /**
     *  Compare with other iterator
     *  @param  that
     *  @return bool
     */
    virtual bool equals(const IteratorImpl *that) const override
    {
        // of course if the objects are identical
        if (this == that) return true;
        
        // cast to traverse-iterator
        TraverseIterator *other = (TraverseIterator *)that;
        
        // if both objects are in an invalid state we consider them to be identical
        if (!_iter && !other->_iter) return true;
        
        // although the iterators could be at the same pos, for simplicity
        // we consider them different here
        return false;
    }

    /**
     *  Derefecence, this returns a std::pair with the current key and value
     *  @return std::pair
     */
    virtual const std::pair<Value,Value> &current() const
    {
        return _data;
    }

private:
    /**
     *  The object that is iterated over
     *  @var    _val
     */
    zval *_object = nullptr;
    
    /**
     *  The iterator from Zend
     *  @var    zend_object_iterator
     */
    struct _zend_object_iterator *_iter = nullptr;

    /**
     *  Current data
     *  @var    pair
     */
    std::pair<Value,Value> _data;


    /**
     *  Read current data
     *  @return bool
     */
    bool read()
    {
        // not possible when no iterator exists
        if (!_iter) return false;

        // is the iterator at a valid position?
        if (_iter->funcs->valid(_iter) == FAILURE) return invalidate();

#if PHP_VERSION_ID >= 50400

        // create a value object
        Value val;
        
        // call the function to get the key
        _iter->funcs->get_current_key(_iter, val._val);
        
        // store the key
        _data.first = val;

#else

        // variable we need for fetching the key, and that will be assigned by
        // the PHP engine (this is php 5.3 code)
        char *str_key; unsigned int str_key_len; unsigned long int_key;

        // php 5.3 code, fetch the current key
        int type = _iter->funcs->get_current_key(_iter, &str_key, &str_key_len, &int_key);
        
        // what sort of key do we have?
        if (type == HASH_KEY_IS_LONG) 
        {
            // we have an int key
            _data.first = (int64_t)int_key;
        }
        else
        {
            // we have a string key that is already allocated
            _data.first = str_key;
            
            // deallocate the data
            efree(str_key);
        }

#endif

        // now we're going to fetch the value, for this we need a strange zval
        // it is strange that this is a pointer-to-pointer, but that is how
        // the Zend engine implements this. It is going to be filled with
        // a pointer to a memory address that is guaranteed to hold a valid
        // zval.
        zval **zval;
        
        // get the current value
        _iter->funcs->get_current_data(_iter, &zval);
        
        // wrap the zval in a value object
        _data.second = Value(*zval);
     
        // done
        return true;
    }
    
    /**
     *  Invalidate the object
     *  @return bool
     */
    bool invalidate()
    {
        // skip if already invalid
        if (!_iter) return false;
        
        // reset the iterator
        _iter->funcs->dtor(_iter);
        
        // set back to null
        _iter = nullptr;
        
        // done
        return false;
    }
};

/**
 *  End namespace
 */
}