From 3953d8833e070fa1d6cc7b264dd19ed56c909f02 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Wed, 9 Apr 2014 10:08:29 +0200 Subject: added support for accessing array keys by other value objects, support for unsetting array members, support for array_key_exists(), isset() and unset() functions (feature request in from issue #71) --- include/call.h | 46 +++++++++++++++----- include/hashmember.h | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++- include/hashparent.h | 39 +++++++++++++++++ include/value.h | 99 ++++++++++++++++++++++++++++++++++++++++++ zend/value.cpp | 55 ++++++++++++++++++++++++ 5 files changed, 345 insertions(+), 12 deletions(-) diff --git a/include/call.h b/include/call.h index 3ad4776..6c5dd8f 100644 --- a/include/call.h +++ b/include/call.h @@ -44,17 +44,41 @@ Value call(const char *name, Params&&... params) * make the code run on the highway), it is not expected that these functions * are going to be used very often anyway. */ -inline Value array_keys(const Value &value) { return call("array_keys", value); } -inline Value array_values(const Value &value) { return call("array_values", value); } -inline Value echo(const char *input) { out << input; return nullptr; } -inline Value echo(const std::string &input) { out << input; return nullptr; } -inline Value empty(const Value &value) { return value.isNull() || !value.boolValue(); } -inline Value empty(const HashMember &member) { return !member.exists() || empty(member.value()); } -inline Value empty(const HashMember &member) { return !member.exists() || empty(member.value()); } -inline Value ini_get(const Value &value) { return call("ini_get", value); } -//inline Value isset(const Value &value) { return call("isset", value); } -//inline Value isset(const HashMember &member) { return member.exists() && isset(member.value()); } -//inline Value isset(const HashMember &member) { return member.exists() && isset(member.value()); } +inline Value array_key_exists(const Value &key, const Value &array) { return array.contains(key); } +inline Value array_key_exists(int key, const Value &array) { return array.contains(key); } +inline Value array_key_exists(const char *key, const Value &array) { return array.contains(key); } +inline Value array_key_exists(const std::string &key, const Value &array) { return array.contains(key); } +inline Value array_keys(const Value &value) { return call("array_keys", value); } +inline Value array_values(const Value &value) { return call("array_values", value); } +inline Value echo(const char *input) { out << input; return nullptr; } +inline Value echo(const std::string &input) { out << input; return nullptr; } +inline Value empty(const Value &value) { return value.isNull() || !value.boolValue(); } +inline Value empty(const HashMember &member) { return !member.exists() || empty(member.value()); } +inline Value empty(const HashMember &member) { return !member.exists() || empty(member.value()); } +inline Value ini_get(const Value &value) { return call("ini_get", value); } +inline Value is_array(const Value &value) { return value.isArray(); } +inline void unset(HashMember &member) { member.unset(); } +inline void unset(HashMember &member) { member.unset(); } +inline void unset(HashMember &member) { member.unset(); } + +/** + * The isset function conflicts with the 'isset' macro defined by the Zend engine + */ +#pragma push_macro("isset"); +#undef isset + +/** + * Define the isset function + */ +inline Value isset(const Value &value) { return call("isset", value); } +inline Value isset(const HashMember &member) { return member.exists() && isset(member.value()); } +inline Value isset(const HashMember &member) { return member.exists() && isset(member.value()); } +inline Value isset(const HashMember &member) { return member.exists() && isset(member.value()); } + +/** + * Re-install the ISSET macro + */ +#pragma pop_macro("isset"); /** * End of namespace diff --git a/include/hashmember.h b/include/hashmember.h index 9a2d5e8..1a29548 100644 --- a/include/hashmember.h +++ b/include/hashmember.h @@ -381,7 +381,7 @@ public: /** * Check if a certain index exists in the array/object - * @param key + * @param index * @return bool */ virtual bool contains(int index) const override @@ -389,6 +389,17 @@ public: // object must exist, and the value must contain the key return exists() && value().contains(index); } + + /** + * Check if a certain index exists in the array/object + * @param key + * @return bool + */ + virtual bool contains(const Value &key) const override + { + // object must exist, and the value must contain the key + return exists() && value().contains(key); + } /** * Retrieve the value at a string index @@ -417,6 +428,20 @@ public: // ask the value return value().get(index); } + + /** + * Retrieve the value at a variant index + * @param key + * @return Value + */ + virtual Value get(const Value &key) const override + { + // return null if it does not exist + if (!exists()) return nullptr; + + // ask the value + return value().get(key); + } /** * Overwrite the value at a certain string index @@ -452,6 +477,97 @@ public: _parent->set(_index, current); } + /** + * Overwrite the value at a certain variant index + * @param key + * @param value + */ + virtual void set(const Value &key, const Value &value) override + { + // get the current value + Value current(this->value()); + + // add the value + current[key] = value; + + // pass this to the base + _parent->set(_index, current); + } + + /** + * Unset the member + */ + void unset() + { + _parent->unset(_index); + } + + /** + * Unset a member by its index + * @param index + */ + virtual void unset(int index) override + { + // if the current property does not even exist, we do not have to add anything + if (!exists()) return; + + // get the current value + Value current(this->value()); + + // skip if the property does not exist + if (!current.contains(index)) return; + + // remove the index + current.unset(index); + + // pass the new value to the base + _parent->set(_index, current); + } + + /** + * Unset a member by its key + * @param key + */ + virtual void unset(const std::string &key) override + { + // if the current property does not even exist, we do not have to add anything + if (!exists()) return; + + // get the current value + Value current(this->value()); + + // skip if the property does not exist + if (!current.contains(key)) return; + + // remove the index + current.unset(key); + + // pass the new value to the base + _parent->set(_index, current); + } + + /** + * Unset a member by its key + * @param key + */ + virtual void unset(const Value &key) override + { + // if the current property does not even exist, we do not have to add anything + if (!exists()) return; + + // get the current value + Value current(this->value()); + + // skip if the property does not exist + if (!current.contains(key)) return; + + // remove the index + current.unset(key); + + // pass the new value to the base + _parent->set(_index, current); + } + protected: /** * Protected copy constructor diff --git a/include/hashparent.h b/include/hashparent.h index 4c2ee68..5ba05bd 100644 --- a/include/hashparent.h +++ b/include/hashparent.h @@ -55,6 +55,13 @@ public: */ virtual bool contains(int index) const = 0; + /** + * Check if a certain index exists in the array/object + * @param key + * @return bool + */ + virtual bool contains(const Value &index) const = 0; + /** * Retrieve the value at a string index * @param key @@ -68,6 +75,13 @@ public: * @return Value */ virtual Value get(int index) const = 0; + + /** + * Retrieve the value at a value index + * @param key + * @return Value + */ + virtual Value get(const Value &key) const = 0; /** * Overwrite the value at a certain string index @@ -83,6 +97,31 @@ public: */ virtual void set(int index, const Value &value) = 0; + /** + * Overwrite the value at a certain variant index + * @param key + * @param value + */ + virtual void set(const Value &key, const Value &value) = 0; + + /** + * Unset a member by its index + * @param index + */ + virtual void unset(int index) = 0; + + /** + * Unset a member by its key + * @param key + */ + virtual void unset(const std::string &key) = 0; + + /** + * Unset a member by its key + * @param key + */ + virtual void unset(const Value &key) = 0; + }; /** diff --git a/include/value.h b/include/value.h index 95985d7..d1e6c8a 100644 --- a/include/value.h +++ b/include/value.h @@ -574,6 +574,18 @@ public: return contains(key, strlen(key)); } + /** + * Is a certain key set in the array, when that key is stored as value object + * @param key + * @return bool + */ + virtual bool contains(const Value &value) const override + { + if (value.isNumeric()) return contains(value.numericValue()); + if (value.isString()) return contains(value.rawValue(), value.size()); + return contains(value.stringValue()); + } + /** * Cast to a number * @return int32_t @@ -691,6 +703,18 @@ public: return get(key.c_str(), key.size()); } + /** + * Get access to a certain variant member + * @param key + * @return Value + */ + virtual Value get(const Value &key) const override + { + if (key.isNumeric()) return get(key.numericValue()); + if (key.isString()) return get(key.rawValue(), key.size()); + return get(key.stringValue()); + } + /** * Set a certain property * Calling this method will turn the value into an array @@ -731,6 +755,61 @@ public: return set(key.c_str(), key.size(), value); } + /** + * Overwrite the value at a certain variant index + * @param key + * @param value + */ + virtual void set(const Value &key, const Value &value) override + { + if (key.isNumeric()) return set(key.numericValue(), value); + if (key.isString()) return set(key.rawValue(), key.size(), value); + return set(key.stringValue(), value); + } + + /** + * Unset a member by its index + * @param index + */ + virtual void unset(int index) override; + + /** + * Unset by key name and length of the key + * @param key + * @param size + */ + void unset(const char *key, int size); + + /** + * Unset by key name and length of the key + * @param key + * @param size + */ + void unset(const char *key) + { + unset(key, strlen(key)); + } + + /** + * Unset a member by its key + * @param key + */ + virtual void unset(const std::string &key) override + { + return unset(key.c_str(), key.size()); + } + + /** + * Unset a member by its key + * @param key + */ + virtual void unset(const Value &key) override + { + if (key.isNumeric()) return unset(key.numericValue()); + if (key.isString()) return unset(key.rawValue(), key.size()); + return unset(key.stringValue()); + } + /** * Array access operator * This can be used for accessing arrays @@ -787,6 +866,26 @@ public: { return get(key); } + + /** + * Index by other value object + * @param key + * @return HashMember + */ + HashMember operator[](const Value &key); + + /** + * Index by other value object + * @param key + * @return HashMember + */ + Value operator[](const Value &key) const + { + if (key.isNumeric()) return get(key.numericValue()); + if (key.isString()) return get(key.rawValue(), key.size()); + return get(key.stringValue()); + } + /** * Call the function in PHP diff --git a/zend/value.cpp b/zend/value.cpp index 2ca1585..a8fadce 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -1840,6 +1840,51 @@ void Value::set(const char *key, int size, const Value &value) setRaw(key, size, value); } +/** + * Unset a member by its index + * @param index + */ +void Value::unset(int index) +{ + // only necessary for arrays + if (!isArray()) return; + + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // remove the index + zend_hash_index_del(Z_ARRVAL_P(_val), index); +} + +/** + * Unset by key name and length of the key + * @param key + * @param size + */ +void Value::unset(const char *key, int size) +{ + // is this an object? + if (isObject()) + { + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // in the zend header files, unsetting properties is redirected to setting it to null... + add_property_null_ex(_val, key, size TSRMLS_CC); + } + else if (isArray()) + { + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // remove the index + zend_hash_del(Z_ARRVAL_P(_val), key, size); + } +} + /** * Array access operator * This can be used for accessing arrays @@ -1851,6 +1896,16 @@ HashMember Value::operator[](int index) return HashMember(this, index); } +/** + * Index by other value object + * @param key + * @return HashMember + */ +HashMember Value::operator[](const Value &key) +{ + return HashMember(this, key); +} + /** * Array access operato * This can be used for accessing associative arrays -- cgit v1.2.3