From 49d88d98a0656233f15923d31ea67a1ed229e514 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sun, 9 Mar 2014 22:37:41 +0100 Subject: work in progress on iterators --- README.md | 12 +- documentation/calling-functions-and-methods.html | 2 +- documentation/exceptions.html | 9 +- documentation/magic-methods-and-interfaces.html | 21 ++- include/class.h | 14 +- include/classbase.h | 19 +- include/interface.h | 14 +- include/iterator.h | 140 +++++++++++++++ include/traversable.h | 8 +- include/value.h | 1 + phpcpp.h | 2 + src/base.cpp | 5 +- src/classbase.cpp | 28 +++ src/includes.h | 2 + src/iterator.cpp | 211 +++++++++++++++++++++++ src/value.cpp | 96 ++++++----- 16 files changed, 511 insertions(+), 73 deletions(-) create mode 100644 include/iterator.h create mode 100644 src/iterator.cpp diff --git a/README.md b/README.md index 24f81ec..601487a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ PHP-CPP The PHP-CPP library is a C++ library for developing PHP extensions. It offers a collection of well documented and easy-to-use classes that can be used and extended to build native -extensions for PHP. +extensions for PHP. The full documentation can be found on http://www.php-cpp.com. Unlike regular PHP extensions - which are really hard to implement and require a deep knowledge of the Zend engine and pointer manipulation - extensions built with PHP-CPP @@ -81,11 +81,5 @@ function get_complex_array() } ``` -However, this library is currently a work in progress, and it is an open -source project. We are looking for people who'd like to contribute to it. - -PHP-CPP is an initiative from Copernica BV. - -For more information, contact me at [emiel.bruijntjes@copernica.com](mailto:emiel.bruijntjes@copernica.com?Subject=PHP-CPP). - -Emiel Bruijntjes (1 September 2013) +More information and more examples are available on the official website: +http://www.php-cpp.com. \ No newline at end of file diff --git a/documentation/calling-functions-and-methods.html b/documentation/calling-functions-and-methods.html index 8e2e7ba..d3bc3e2 100644 --- a/documentation/calling-functions-and-methods.html +++ b/documentation/calling-functions-and-methods.html @@ -114,7 +114,7 @@ extern "C" { is used in the example to call the PHP method DateTime::format().

- In PHP scripts you can create an array with two members: and object and + In PHP scripts you can create an array with two members: an object and the name of a method. This array can then be used as if it was a regular function. You can do similar things in C++, as we showed in the example with the "time_format" variable. diff --git a/documentation/exceptions.html b/documentation/exceptions.html index f8af4d0..56e7a31 100644 --- a/documentation/exceptions.html +++ b/documentation/exceptions.html @@ -12,6 +12,11 @@

#include <phpcpp.h>
 
+/**
+ *  Simple function that takes two numeric parameters,
+ *  and that divides them. Division by zero is of course
+ *  not permitted - it will throw an exception then
+ */
 Php::Value myDiv(Php::Parameters ¶ms)
 {
     // division by zero is not permitted, throw an exception when this happens
@@ -75,7 +80,7 @@ exception caught
 

Catching exceptions in C++

- And this works the other way around too. If your extensions calls a PHP + And this works the other way around too. If your extension calls a PHP function, and that PHP function happens to throw an exception, you can catch it just as if it was a normal C++ exception.

@@ -111,7 +116,7 @@ extern "C" { be used just like a normal PHP $variable is used, and you can thus store integers, strings, objects, arrays, et cetera in it. But this also means that you can use it to store functions - because PHP variables can - be used to store function too! And that's exactly what we're doing here. + be used to store functions too! And that's exactly what we're doing here.

The callMe() function from this example extension receives one single diff --git a/documentation/magic-methods-and-interfaces.html b/documentation/magic-methods-and-interfaces.html index 93c76a6..6ece6a5 100644 --- a/documentation/magic-methods-and-interfaces.html +++ b/documentation/magic-methods-and-interfaces.html @@ -159,11 +159,11 @@ echo(count($counter)."\n");

A PHP object can be turned into a variable that behaves like an array by implementing the Php::ArrayAccess interface. When you do this, objects - can be accessed by with the array access operator ($object["property"]). + can be accessed with array access operators ($object["property"]).

In the following example we use the Php::Countable and the Php::ArrayAccess - interfaces to create a class that acts like an associative array than can + interfaces to create an associative array class than can be used for storing strings (remember: this is just an example, PHP already has support for associative arrays, so it is debatable how useful the example is). @@ -278,10 +278,12 @@ extern "C" { to be forwarded to a regular C++ std::map object.

- The Map object from the example does not have any regular methods at all. - It only implements the Php::Countable interface and Php::ArrayAccess interface, - so it is perfectly usable to store and retrieve properties, but it does not - have any methods. The following script shows how to use it. + Inside the get_module() function, the Map is registered and added to the + extension. But unlike many other examples, none of the class methods are + exported to PHP. It only implements the Php::Countable interface and + Php::ArrayAccess interface, so it is perfectly usable to store and retrieve + properties, but from a PHP script it does not have any callable methods. + The following script shows how to use it.


@@ -292,7 +294,7 @@ $map = new Map();
 // store some values
 $map["a"] = 1234;
 $map["b"] = "xyz";
-$map["c"] = new stdClass();
+$map["c"] = 0;
 
 // show the values
 echo($map["a"]."\n");
@@ -301,6 +303,11 @@ echo($map["c"]."\n");
 
 // access a value that does not exist
 echo($map["d"]."\n");
+
 ?>
 

+

+ The output speaks for itself. The map has three members, "1234" (a string + variable), "xyz" and "0". +

diff --git a/include/class.h b/include/class.h index e9a7179..6bcefda 100644 --- a/include/class.h +++ b/include/class.h @@ -154,7 +154,7 @@ private: * Construct a new instance of the object * @return Base */ - virtual Base* construct() override + virtual Base* construct() const override { // construct an instance return new T(); @@ -165,7 +165,7 @@ private: * @param orig * @return Base */ - virtual Base *clone(Base *orig) override + virtual Base *clone(Base *orig) const override { // cast to the original object T *t = (T *)orig; @@ -174,6 +174,16 @@ private: return new T(*t); } + /** + * Is this class traversable? + * @return bool + */ + virtual bool traversable() const override + { + // check if the templated class overrides from the base + return std::is_base_of::value; + } + /** * Namespaces have access to the private base class */ diff --git a/include/classbase.h b/include/classbase.h index 81454ba..1be2538 100644 --- a/include/classbase.h +++ b/include/classbase.h @@ -97,14 +97,20 @@ public: * Construct a new instance of the object * @return Base */ - virtual Base* construct() = 0; + virtual Base* construct() const = 0; /** * Create a clone of an object * @param orig * @return Base */ - virtual Base *clone(Base *orig) = 0; + virtual Base *clone(Base *orig) const = 0; + + /** + * Is this a traversable class? + * @return bool + */ + virtual bool traversable() const = 0; /** * Initialize the class, given its name @@ -282,6 +288,15 @@ private: */ static struct _zend_object_handlers *objectHandlers(); + /** + * Function to create a new iterator to iterate over an object + * @param entry The class entry + * @param object The object to iterate over + * @param by_ref ????? + * @return zend_object_iterator* Pointer to the iterator + */ + static struct _zend_object_iterator *getIterator(struct _zend_class_entry *entry, struct _zval_struct *object, int by_ref); + /** * Name of the class * @var string diff --git a/include/interface.h b/include/interface.h index b3031e9..bdff75d 100644 --- a/include/interface.h +++ b/include/interface.h @@ -43,7 +43,7 @@ private: * Construct a new instance of the object * @return Base */ - virtual Base* construct() override + virtual Base* construct() const override { // this does not occur for interfaces return nullptr; @@ -54,11 +54,21 @@ private: * @param orig * @return Base */ - virtual Base* clone(Base *orig) override + virtual Base* clone(Base *orig) const override { // this does not occur for interfaces return nullptr; } + + /** + * Is this a traversable interface? + * @return bool + */ + virtual bool traversable() const override + { + // interfaces are never traversed + return false; + } /** * Namespaces have access to the private base class diff --git a/include/iterator.h b/include/iterator.h new file mode 100644 index 0000000..3c8f870 --- /dev/null +++ b/include/iterator.h @@ -0,0 +1,140 @@ +/** + * Iterator.h + * + * Base class for iterators. Extension writers that want to create traversable + * classes, should override this class and implement all pure virtual methods + * in it. + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ + +/** + * Forward declarations + */ +struct _zend_object_iterator_funcs; + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class Iterator +{ +public: + /** + * Constructor + * @param base Class over which the iterator is iterating + */ + Iterator(Base *base) : _object(base) {} + + /** + * Destructor + */ + virtual ~Iterator() {} + + /** + * Is the iterator on a valid position + * @return bool + */ + virtual bool valid() = 0; + + /** + * The value at the current position + * @return Value + */ + virtual Value current() = 0; + + /** + * The key at the current position + * @return Value + */ + virtual Value key() = 0; + + /** + * Move to the next position + */ + virtual void next() = 0; + + /** + * Rewind the iterator to the front position + */ + virtual void rewind() = 0; + +private: + /** + * During the lifetime of the iterator, the object over which + * it iterates is keps as a private variable. This ensures that + * this object is not destructed as long as the iterator exists + * @var Value + */ + Value _object; + + /** + * Internal method that returns the implementation object + * @return zend_object_iterator + */ + struct _zend_object_iterator *implementation(); + + /** + * Iterator destructor method + * @param iter + */ + static void destructor(struct _zend_object_iterator *iter); + + /** + * Iterator valid function + * Returns FAILURE or SUCCESS + * @param iter + * @return int + */ + static int valid(struct _zend_object_iterator *iter); + + /** + * Fetch the current item + * @param iter + * @param data + */ + static void current(struct _zend_object_iterator *iter, struct _zval_struct ***data); + + /** + * Fetch the key for the current element (optional, may be NULL). The key + * should be written into the provided zval* using the ZVAL_* macros. If + * this handler is not provided auto-incrementing integer keys will be + * used. + * @param iter + * @param data + */ + static void key(struct _zend_object_iterator *iter, struct _zval_struct *data); + + /** + * Step forwards to the next element + * @param iter + */ + static void next(struct _zend_object_iterator *iter); + + /** + * Rewind the iterator back to the start + * @param iter + */ + static void rewind(struct _zend_object_iterator *iter); + + /** + * Get access to all iterator functions + * @return zend_object_iterator_funcs + */ + static struct _zend_object_iterator_funcs *functions(); + + /** + * Classbase is a friend + */ + friend class ClassBase; +}; + +/** + * End namespace + */ +} diff --git a/include/traversable.h b/include/traversable.h index 57cc9bc..f81a5b4 100644 --- a/include/traversable.h +++ b/include/traversable.h @@ -19,8 +19,12 @@ namespace Php { */ class Traversable { - - +public: + /** + * Retrieve an instance of the iterator + * @return Iterator + */ + virtual Iterator *getIterator() = 0; }; /** diff --git a/include/value.h b/include/value.h index a1dd593..2ffbd7c 100644 --- a/include/value.h +++ b/include/value.h @@ -862,6 +862,7 @@ protected: friend class Globals; friend class Member; friend class ClassBase; + friend class Iterator; }; /** diff --git a/phpcpp.h b/phpcpp.h index 207d329..15a5edd 100644 --- a/phpcpp.h +++ b/phpcpp.h @@ -42,6 +42,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/src/base.cpp b/src/base.cpp index a96a6ce..305821f 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -50,8 +50,10 @@ MixedObject *Base::store(zend_class_entry *entry) // store the class entry in the newly created object result->php.ce = entry; + + // @todo is this really necessary - and when do we destruct this data? + // (if we remove this code, everything breaks down in a for ($object as $k => $v) loop) -#if PHP_VERSION_ID < 50399 // the original create_object fills the initial object with the default properties, // we're going to do exactly the same. start with setting up a hashtable for the props ALLOC_HASHTABLE(result->php.properties); @@ -59,6 +61,7 @@ MixedObject *Base::store(zend_class_entry *entry) // initialize the hash table zend_hash_init(result->php.properties, 0, NULL, ZVAL_PTR_DTOR, 0); +#if PHP_VERSION_ID < 50399 // initialize the properties zend_hash_copy(result->php.properties, &entry->default_properties, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval*)); #else diff --git a/src/classbase.cpp b/src/classbase.cpp index fe08bd1..8926b3e 100644 --- a/src/classbase.cpp +++ b/src/classbase.cpp @@ -358,6 +358,30 @@ zend_object_value ClassBase::createObject(zend_class_entry *entry TSRMLS_DC) return result; } +/** + * Function to create a new iterator to iterate over an object + * @param entry The class entry + * @param object The object to iterate over + * @param by_ref ????? + * @return zend_object_iterator* Pointer to the iterator + */ +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"); + + // retrieve the traversable object + Traversable *traversable = dynamic_cast(cpp_object(object)); + + // create an iterator + auto *iterator = traversable->getIterator(); + + // return the implementation + return iterator->implementation(); +} + /** * Destructor */ @@ -436,6 +460,10 @@ void ClassBase::initialize(const std::string &prefix) // we need a special constructor entry.create_object = &ClassBase::createObject; + // and a special function for retrieving the iterator (but only if this is + // a traversable class) + if (traversable()) entry.get_iterator = &ClassBase::getIterator; + // register the class _entry = zend_register_internal_class(&entry TSRMLS_CC); diff --git a/src/includes.h b/src/includes.h index 67a7749..24b4837 100644 --- a/src/includes.h +++ b/src/includes.h @@ -60,6 +60,8 @@ #include "../include/base.h" #include "../include/countable.h" #include "../include/arrayaccess.h" +#include "../include/iterator.h" +#include "../include/traversable.h" #include "../include/classtype.h" #include "../include/classbase.h" #include "../include/class.h" diff --git a/src/iterator.cpp b/src/iterator.cpp new file mode 100644 index 0000000..c0e9c3d --- /dev/null +++ b/src/iterator.cpp @@ -0,0 +1,211 @@ +/** + * IteratorImpl.cpp + * + * Implementation file of the IteratorImpl class + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Iterator destructor method + * @param iter + */ +void Iterator::destructor(zend_object_iterator *iter) +{ + std::cout << "destruct iterator" << std::endl; + + // get the actual iterator + Iterator *iterator = (Iterator *)iter->data; + + // delete the iterator + delete iterator; + + // free memory for the meta object + efree(iter); +} + +/** + * Iterator valid function + * Returns FAILURE or SUCCESS + * @param iter + * @return int + */ +int Iterator::valid(zend_object_iterator *iter) +{ + std::cout << "Iterator::valid" << std::endl; + + // get the actual iterator + Iterator *iterator = (Iterator *)iter->data; + + // check if valid + return iterator->valid() ? SUCCESS : FAILURE; +} + +/** + * Fetch the current item + * @param iter + * @param data + */ +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()); + + std::cout << "detach value " << value << std::endl; + + zval *val = value.detach(); + + // copy the value + *data = &val; +} + +/** + * Fetch the key for the current element (optional, may be NULL). The key + * should be written into the provided zval* using the ZVAL_* macros. If + * this handler is not provided auto-incrementing integer keys will be + * used. + * @param iter + * @param retval + */ +void Iterator::key(zend_object_iterator *iter, zval *retval) +{ + // 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; + +// zval *zval = key.detach(); + + + 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; +} + +/** + * Step forwards to the next element + * @param iter + */ +void Iterator::next(zend_object_iterator *iter) +{ + std::cout << "Iterator::next" << std::endl; + + // get the actual iterator + Iterator *iterator = (Iterator *)iter->data; + + // call the next method + iterator->next(); +} + +/** + * Rewind the iterator back to the start + * @param iter + */ +void Iterator::rewind(zend_object_iterator *iter) +{ + std::cout << "Iterator::rewind" << std::endl; + + // get the actual iterator + Iterator *iterator = (Iterator *)iter->data; + + // call the rewind method + iterator->rewind(); +} + +/** + * Get access to all iterator functions + * @return zend_object_iterator_funcs + */ +zend_object_iterator_funcs *Iterator::functions() +{ + // static variable with all functions + static zend_object_iterator_funcs funcs; + + // static variable that knows if the funcs are already initialized + static bool initialized = false; + + // no need to set anything if already initialized + if (initialized) return &funcs; + + // set the members + funcs.dtor = &Iterator::destructor; + funcs.valid = &Iterator::valid; + funcs.get_current_data = &Iterator::current; + funcs.get_current_key = &Iterator::key; + funcs.move_forward = &Iterator::next; + funcs.rewind = &Iterator::rewind; + + // invalidate is not yet supported + funcs.invalidate_current = nullptr; + + // remember that functions are initialized + initialized = true; + + // done + return &funcs; +} + +/** + * Internal method that returns the implementation object + * @return zend_object_iterator + */ +struct _zend_object_iterator *Iterator::implementation() +{ + // create an iterator + zend_object_iterator *iterator = (zend_object_iterator *)emalloc(sizeof(zend_object_iterator)); + + // initialize all properties + iterator->data = this; + iterator->index = 0; + iterator->funcs = functions(); + + // done + return iterator; +} + +/** + * End namespace + */ +} + diff --git a/src/value.cpp b/src/value.cpp index 752d77e..f8ee3e9 100644 --- a/src/value.cpp +++ b/src/value.cpp @@ -1467,67 +1467,73 @@ int Value::size() const */ std::map Value::mapValue() const { - // loop through the zval key/value pairs, and return a map // result variable std::map result; // check type - if (isArray() || isObject()) + if (isArray()) { - zval **value; - char *key; - unsigned long ind; + // get access to the hast table + HashTable *arr = Z_ARRVAL_P(_val); - // get access to the internal hash table of _val - // see Zend/zend_API.h 723: HASH_OF(_val) - HashTable *arr = isArray() ? Z_ARRVAL_P(_val) : Z_OBJ_HT_P(_val)->get_properties((_val) TSRMLS_CC); + // 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; - // similarly php: reset($array): - // The definition of this and the following functions can be found in Zend/zend_hash.h 174 - // Maybe make it optional? - // If the following line to remove, then repeated calling the Value::mapValue() will return an empty map - zend_hash_internal_pointer_reset(arr); + // key type + int hash_key_type; - if (isArray()) + // loop through the recors + while ((hash_key_type = zend_hash_get_current_key(arr, &key, &ind, 0)) != HASH_KEY_NON_EXISTENT) { - unsigned int hash_key_type; - while( (hash_key_type = zend_hash_get_current_key(arr, &key, &ind, 0)) != HASH_KEY_NON_EXISTENT ) - { - zend_hash_get_current_data(arr, (void **) &value); + // required variable + zval **value; + + // retrieve data + zend_hash_get_current_data(arr, (void **) &value); - if(HASH_KEY_IS_LONG == hash_key_type) - { - result[std::to_string(ind)] = Value(*value); - } - else // hash_key_type == HASH_KEY_IS_STRING - { - result[key] = Value(*value); - } - - // next iteration - zend_hash_move_forward(arr); - } + // 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); + + // next iteration + zend_hash_move_forward(arr); } - else + } + else if (isObject()) + { + // get access to the hast table + HashTable *arr = Z_OBJ_HT_P(_val)->get_properties(_val); + + // 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 ) { - // For obtaining a hashtable of the object meets function void rebuild_object_properties(zend_object *zobj) - // Zend/zend_object_handlers.c 66 - // hashtable of object's properties always has string (no integer) keys - while( zend_hash_get_current_key(arr, &key, &ind, 0) != HASH_KEY_NON_EXISTENT ) + // if property is accessible (i.e. propertie access type is public. See rebuild_object_properties ) + if('\0' != *key) { - // if propertie is accessible (i.e. propertie access type is public. See rebuild_object_properties ) - if('\0' != *key) - { - zend_hash_get_current_data(arr, (void **) &value); - result[key] = Value(*value); - } - - // next iteration - zend_hash_move_forward(arr); + // required variable + zval **value; + + // retrieve property + zend_hash_get_current_data(arr, (void **) &value); + + // append to mape + result[key] = Value(*value); } - } + // next iteration + zend_hash_move_forward(arr); + } } // done -- cgit v1.2.3