diff options
-rw-r--r-- | documentation/magic-methods-and-interfaces.html | 274 | ||||
-rw-r--r-- | include/iterator.h | 20 | ||||
-rw-r--r-- | src/classbase.cpp | 2 | ||||
-rw-r--r-- | src/iterator.cpp | 100 | ||||
-rw-r--r-- | src/value.cpp | 79 |
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> +<?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"); +} +?> +</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 <phpcpp.h> + +/** + * 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<std::string,std::string> + */ + const std::map<std::string,std::string> &_map; + + /** + * The actual C++ iterator + * @var std::map<std::string,std::string>l;::const_iterator; + */ + std::map<std::string,std::string>::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<std::string,std::string> &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<std::string,std::string> + */ + std::map<std::string,std::string> _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<Map> 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 |