summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2014-03-10 10:39:22 +0100
committerEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2014-03-10 10:39:22 +0100
commitd2e10c764d1b8860dd798eda3055fc957ff556ad (patch)
tree10e6d19cdc71f27e08e0134ad30fe34cd05b7046
parent49d88d98a0656233f15923d31ea67a1ed229e514 (diff)
fixed iterators for php 5.3 + updated documentation about iterators
-rw-r--r--documentation/magic-methods-and-interfaces.html274
-rw-r--r--include/iterator.h20
-rw-r--r--src/classbase.cpp2
-rw-r--r--src/iterator.cpp100
-rw-r--r--src/value.cpp79
5 files changed, 395 insertions, 80 deletions
diff --git a/documentation/magic-methods-and-interfaces.html b/documentation/magic-methods-and-interfaces.html
index 6ece6a5..244ebcd 100644
--- a/documentation/magic-methods-and-interfaces.html
+++ b/documentation/magic-methods-and-interfaces.html
@@ -219,7 +219,7 @@ public:
*/
virtual void offsetSet(const Php::Value &key, const Php::Value &value) override
{
- _map[key] = value;
+ _map[key] = value.stringValue();
}
/**
@@ -311,3 +311,275 @@ echo($map["d"]."\n");
The output speaks for itself. The map has three members, "1234" (a string
variable), "xyz" and "0".
</p>
+<h2>The Traversable interface</h2>
+<p>
+ Classes can also be used in foreach loops, just like regular arrays. If you
+ want to enable this feature, your class should extend from the Php::Traverable
+ base class and implement the getIterator() method.
+</p>
+<p>
+<pre class="language-php"><code>
+&lt;?php
+// fill a map
+$map = new Map();
+$map["a"] = 1234;
+$map["b"] = 5678;
+
+// iterate over it
+foreach ($map as $key => $value)
+{
+ // output the key and value
+ echo("$key: $value\n");
+}
+?&gt;
+</code></pre>
+</p>
+<p>
+ The PHP-CPP library implements iterators in a slightly different manner than
+ the SPL does, and that you are used to if you have been working with PHP.
+ In PHP, to make a class traversable (usable in foreach loops), you have to
+ implement either the Iterator interface, or the IteratorAggregate interface.
+ This is a peculiar architecture - if not to say faulty. When you think of it,
+ it is not the container object itself that is the iterator, that container object is
+ only iterat<i>able</i>! It is <i>being iterated over</i>. In our above example,
+ the $map variable is not the actual iterator, but the container that is
+ iterated over. The real iterator is a hidden object that is not exposed to
+ your PHP script and that controls the foreach loop. The SPL however, would call
+ the map an iterator too.
+</p>
+<p>
+ In PHP-CPP we have therefore decided not to follow the SPL API,
+ and create a completely new way of implementing traversable classes.
+ To make a class traversable, it must be extended from the Php::Traversable
+ base class, that forces you to implement the getIterator() method. This
+ method should return a Php::Iterator instance.
+</p>
+<p>
+ The Php::Iterator object has five methods that are needed for
+ running the foreach loop. Note that your Iterator class does not have to
+ be a class that is accessible from PHP, and does not have to be derived from
+ Php::Base. It is an internal class that is used by foreach loops.
+</p>
+<p>
+<pre class="language-c++"><code>
+#include &lt;phpcpp.h&gt;
+
+/**
+ * A sample iterator class that can be used to iterate
+ * over a map of strings
+ */
+class MapIterator : public Php::Iterator
+{
+private:
+ /**
+ * The map that is being iterated over
+ * This is a reference to the actual map
+ * @var std::map&lt;std::string,std::string&gt;
+ */
+ const std::map&lt;std::string,std::string&gt; &_map;
+
+ /**
+ * The actual C++ iterator
+ * @var std::map&lt;std::string,std::string&gtl;::const_iterator;
+ */
+ std::map&lt;std::string,std::string&gt;::const_iterator _iter;
+
+public:
+ /**
+ * Constructor
+ * @param object The object that is being iterated over
+ * @param map The internal C++ map that is being iterated over
+ */
+ MapIterator(Php::Base *object, const std::map&lt;std::string,std::string&gt; &map) :
+ Php::Iterator(object), _map(map), _iter(map.begin()) {}
+
+ /**
+ * Destructor
+ */
+ virtual ~MapIterator() {}
+
+ /**
+ * Is the iterator on a valid position
+ * @return bool
+ */
+ virtual bool valid() override
+ {
+ return _iter != _map.end();
+ }
+
+ /**
+ * The value at the current position
+ * @return Value
+ */
+ virtual Php::Value current() override
+ {
+ return _iter->second;
+ }
+
+ /**
+ * The key at the current position
+ * @return Value
+ */
+ virtual Php::Value key() override
+ {
+ return _iter->first;
+ }
+
+ /**
+ * Move to the next position
+ */
+ virtual void next() override
+ {
+ _iter++;
+ }
+
+ /**
+ * Rewind the iterator to the front position
+ */
+ virtual void rewind() override
+ {
+ _iter = _map.begin();
+ }
+};
+
+
+/**
+ * A sample Map class, that can be used to map string-to-strings
+ */
+class Map :
+ public Php::Base,
+ public Php::Countable,
+ public Php::ArrayAccess,
+ public Php::Traversable
+{
+private:
+ /**
+ * Internally, a C++ map is used
+ * @var std::map&lt;std::string,std::string&gt;
+ */
+ std::map&lt;std::string,std::string&gt; _map;
+
+public:
+ /**
+ * C++ constructor and C++ destructpr
+ */
+ Map() {}
+ virtual ~Map() {}
+
+ /**
+ * Method from the Php::Countable interface that
+ * returns the number of elements in the map
+ * @return long
+ */
+ virtual long count() override
+ {
+ return _map.size();
+ }
+
+ /**
+ * Method from the Php::ArrayAccess interface that is
+ * called to check if a certain key exists in the map
+ * @param key
+ * @return bool
+ */
+ virtual bool offsetExists(const Php::Value &key) override
+ {
+ return _map.find(key) != _map.end();
+ }
+
+ /**
+ * Set a member
+ * @param key
+ * @param value
+ */
+ virtual void offsetSet(const Php::Value &key, const Php::Value &value) override
+ {
+ _map[key] = value.stringValue();
+ }
+
+ /**
+ * Retrieve a member
+ * @param key
+ * @return value
+ */
+ virtual Php::Value offsetGet(const Php::Value &key) override
+ {
+ return _map[key];
+ }
+
+ /**
+ * Remove a member
+ * @param key
+ */
+ virtual void offsetUnset(const Php::Value &key) override
+ {
+ _map.erase(key);
+ }
+
+ /**
+ * Get the iterator
+ * @return Php::Iterator
+ */
+ virtual Php::Iterator *getIterator() override
+ {
+ // construct a new map iterator on the heap
+ // the (PHP-CPP library will delete it when ready)
+ return new MapIterator(this, _map);
+ }
+};
+
+/**
+ * Switch to C context to ensure that the get_module() function
+ * is callable by C programs (which the Zend engine is)
+ */
+extern "C" {
+ /**
+ * Startup function that is called by the Zend engine
+ * to retrieve all information about the extension
+ * @return void*
+ */
+ PHPCPP_EXPORT void *get_module() {
+
+ // extension object
+ static Php::Extension myExtension("my_extension", "1.0");
+
+ // description of the class so that PHP knows
+ // which methods are accessible
+ Php::Class&lt;Map&gt; map("Map");
+
+ // add the class to the extension
+ myExtension.add(std::move(map));
+
+ // return the extension
+ return myExtension;
+ }
+}
+</code></pre>
+</p>
+<p>
+ The above example further extends the Map class. It now implements
+ Php::Countable, Php::ArrayAccess and Php::Traversable. This means that
+ Map objects can now also be used inside foreach loop to iterate over the
+ properties.
+</p>
+<p>
+ For this to work, we has to add the Php::Traversable class as base class
+ to the Map class, and implement the getIterator() method. This method
+ returns a new MapIterator class, which is allocated on the heap. Don't
+ worry about memory management: the PHP-CPP library will destruct your
+ operator the moment the foreach loop is finished.
+</p>
+<p>
+ The MapIterator class is derived from the Php::Iterator class, and
+ implements the five methods that are needed for running foreach
+ loops (current(), key(), next(), rewind() and valid()). Note that the
+ base Php::Iterator class expects that the object over which it iterates
+ is passed to the constructor. This is required so that the iterator object
+ can ensure that this iterated object stays in scope for as long as the
+ iterator exists.
+</p>
+<p>
+ Our MapIterator implementation internally is just a small wrapper around
+ a C++ iterator class. It is of course up to you to create more complex
+ implementation when needed.
+</p>
diff --git a/include/iterator.h b/include/iterator.h
index 3c8f870..0c64133 100644
--- a/include/iterator.h
+++ b/include/iterator.h
@@ -74,6 +74,16 @@ private:
Value _object;
/**
+ * The current() method that is called by the Zend engine wants a
+ * pointer-to-pointer-to-a-zval. Because of this, we have to keep the
+ * current value in memory after the current() method returns because
+ * the pointer would otherwise fall out of scope. This is (once again)
+ * odd behavior of the Zend engine, but we'll have to live with that
+ * @var Value
+ */
+ Value _current;
+
+ /**
* Internal method that returns the implementation object
* @return zend_object_iterator
*/
@@ -111,6 +121,16 @@ private:
static void key(struct _zend_object_iterator *iter, struct _zval_struct *data);
/**
+ * Function to retrieve the current key, php 5.3 style
+ * @param iter
+ * @param str_key
+ * @param str_key_len
+ * @param int_key
+ * @return HASH_KEY_IS_STRING or HASH_KEY_IS_LONG
+ */
+ static int key(struct _zend_object_iterator *iter, char **str_key, uint *str_key_len, ulong *int_key);
+
+ /**
* Step forwards to the next element
* @param iter
*/
diff --git a/src/classbase.cpp b/src/classbase.cpp
index 8926b3e..2ea813f 100644
--- a/src/classbase.cpp
+++ b/src/classbase.cpp
@@ -367,8 +367,6 @@ zend_object_value ClassBase::createObject(zend_class_entry *entry TSRMLS_DC)
*/
zend_object_iterator *ClassBase::getIterator(zend_class_entry *entry, zval *object, int by_ref)
{
- std::cout << "call to getIterator" << std::endl;
-
// by-ref is not possible (copied from SPL)
if (by_ref) throw Php::Exception("Foreach by ref is not possible");
diff --git a/src/iterator.cpp b/src/iterator.cpp
index c0e9c3d..a6c7e01 100644
--- a/src/iterator.cpp
+++ b/src/iterator.cpp
@@ -19,8 +19,6 @@ namespace Php {
*/
void Iterator::destructor(zend_object_iterator *iter)
{
- std::cout << "destruct iterator" << std::endl;
-
// get the actual iterator
Iterator *iterator = (Iterator *)iter->data;
@@ -39,8 +37,6 @@ void Iterator::destructor(zend_object_iterator *iter)
*/
int Iterator::valid(zend_object_iterator *iter)
{
- std::cout << "Iterator::valid" << std::endl;
-
// get the actual iterator
Iterator *iterator = (Iterator *)iter->data;
@@ -55,20 +51,15 @@ int Iterator::valid(zend_object_iterator *iter)
*/
void Iterator::current(zend_object_iterator *iter, zval ***data)
{
- std::cout << "get current value " << std::endl;
-
// get the actual iterator
Iterator *iterator = (Iterator *)iter->data;
- // retrieve the value
- Value value(iterator->current());
+ // retrieve the value (and store it in a member so that it is not
+ // destructed when the function returns)
+ iterator->_current = iterator->current();
- std::cout << "detach value " << value << std::endl;
-
- zval *val = value.detach();
-
// copy the value
- *data = &val;
+ *data = &iterator->_current._val;
}
/**
@@ -77,50 +68,57 @@ void Iterator::current(zend_object_iterator *iter, zval ***data)
* this handler is not provided auto-incrementing integer keys will be
* used.
* @param iter
- * @param retval
+ * @param key
*/
-void Iterator::key(zend_object_iterator *iter, zval *retval)
+void Iterator::key(zend_object_iterator *iter, zval *key)
{
// get the actual iterator
Iterator *iterator = (Iterator *)iter->data;
- // wrap data into a result object
-// Value result(data);
-
-// ZVAL_LONG(data, 123);
-
-// return;
-// std::cout << "retrieve key " << result.refcount() << std::endl;
-
- // retrieve the key as key
- Value keyValue = iterator->key();
-
- std::cout << "got key " << keyValue << " " << keyValue.refcount() << std::endl;
+ // retrieve the key
+ Value retval(iterator->key());
-// zval *zval = key.detach();
+ // detach the underlying zval
+ zval *zval = retval.detach();
+
+ // copy it to the key
+ ZVAL_ZVAL(key, zval, 1, 1);
+}
+/**
+ * Function to retrieve the current key, php 5.3 style
+ * @param iter
+ * @param str_key
+ * @param str_key_len
+ * @param int_key
+ * @return HASH_KEY_IS_STRING or HASH_KEY_IS_LONG
+ */
+int Iterator::key(zend_object_iterator *iter, char **str_key, uint *str_key_len, ulong *int_key)
+{
+ // get the actual iterator
+ Iterator *iterator = (Iterator *)iter->data;
- std::cout << "ret key " << retval << " " << Z_REFCOUNT_P(retval) << std::endl;
-
- ZVAL_LONG(retval, rand());
-
-// ZVAL_ZVAL(data, zval, 1, 1);
-
- return;
-
- // copy the key into the other zval, but we use a string or numeric for
- // this operation, because we have looked at the implementation of Value
- // and assigning a full value to the result variable will cause the zval
- // to be destructed and re-allocated (which we do not need)
-// if (key.isString()) ZVAL_STRING(data, key.stringValue();
-// else ZVAL_LONG(data, key.numericValue());
-//
-// std::cout << "key is copied" << std::endl;
-//
-// // detach from result
-// result.detach();
-//
- std::cout << "detached" << std::endl;
+ // retrieve the key
+ Value retval(iterator->key());
+
+ // is this a numeric string?
+ if (retval.isString())
+ {
+ // copy the key and the from the value
+ *str_key = estrndup(retval.rawValue(), retval.size());
+ *str_key_len = retval.size() + 1;
+
+ // done
+ return HASH_KEY_IS_STRING;
+ }
+ else
+ {
+ // convert to a numeric
+ *int_key = retval.numericValue();
+
+ // done
+ return HASH_KEY_IS_LONG;
+ }
}
/**
@@ -129,8 +127,6 @@ void Iterator::key(zend_object_iterator *iter, zval *retval)
*/
void Iterator::next(zend_object_iterator *iter)
{
- std::cout << "Iterator::next" << std::endl;
-
// get the actual iterator
Iterator *iterator = (Iterator *)iter->data;
@@ -144,8 +140,6 @@ void Iterator::next(zend_object_iterator *iter)
*/
void Iterator::rewind(zend_object_iterator *iter)
{
- std::cout << "Iterator::rewind" << std::endl;
-
// get the actual iterator
Iterator *iterator = (Iterator *)iter->data;
diff --git a/src/value.cpp b/src/value.cpp
index f8ee3e9..a78c2d4 100644
--- a/src/value.cpp
+++ b/src/value.cpp
@@ -1479,25 +1479,43 @@ std::map<std::string,Php::Value> Value::mapValue() const
// reset iterator to beginning of the hash table
zend_hash_internal_pointer_reset(arr);
- // pointer that will be set to hash key and index
- char *key;
- unsigned long ind;
+ // loop through the records
+ while (true)
+ {
+ // pointer that will be set to hash key and index
+ char *key;
+ unsigned long ind;
- // key type
- int hash_key_type;
+ // get current key
+ int hash_key_type = zend_hash_get_current_key(arr, &key, &ind, 0);
- // loop through the recors
- while ((hash_key_type = zend_hash_get_current_key(arr, &key, &ind, 0)) != HASH_KEY_NON_EXISTENT)
- {
// required variable
zval **value;
- // retrieve data
- zend_hash_get_current_data(arr, (void **) &value);
+ // check the type
+ switch (hash_key_type) {
+
+ // was it a string?
+ case HASH_KEY_IS_STRING:
- // check the type of key
- if (HASH_KEY_IS_LONG != hash_key_type) result[key] = Value(*value);
- else result[std::to_string(ind)] = Value(*value);
+ // retrieve data, and add to result
+ zend_hash_get_current_data(arr, (void **) &value);
+ result[key] = Value(*value);
+ break;
+
+ // was it numeric?
+ case HASH_KEY_IS_LONG:
+
+ // retrieve data, and add to result
+ zend_hash_get_current_data(arr, (void **) &value);
+ result[std::to_string(ind)] = Value(*value);
+ break;
+
+ default:
+
+ // we're ready
+ return result;
+ }
// next iteration
zend_hash_move_forward(arr);
@@ -1511,24 +1529,37 @@ std::map<std::string,Php::Value> Value::mapValue() const
// reset iterator to beginning of the hash table
zend_hash_internal_pointer_reset(arr);
- // pointer that will be set to hash key and index
- char *key;
- unsigned long ind;
-
// loop through the records
- while( zend_hash_get_current_key(arr, &key, &ind, 0) != HASH_KEY_NON_EXISTENT )
+ while (true)
{
- // if property is accessible (i.e. propertie access type is public. See rebuild_object_properties )
- if('\0' != *key)
- {
+ // pointer that will be set to hash key and index
+ char *key;
+ unsigned long ind;
+
+ // get current key
+ int hash_key_type = zend_hash_get_current_key(arr, &key, &ind, 0);
+
+ // check the type
+ switch (hash_key_type) {
+
+ // was it a string?
+ case HASH_KEY_IS_STRING:
+
+ // inaccessible properties (privates) start with a null character
+ if (*key == '\0') break;
+
// required variable
zval **value;
- // retrieve property
+ // retrieve data, and add to result
zend_hash_get_current_data(arr, (void **) &value);
-
- // append to mape
result[key] = Value(*value);
+ break;
+
+ default:
+
+ // we're ready
+ return result;
}
// next iteration