From f3759b7f3a8dbb7d28444586e9bdfc8c326681a3 Mon Sep 17 00:00:00 2001 From: Bram van den Brink Date: Thu, 23 Jun 2022 10:54:48 +0200 Subject: [PATCH 1/5] added public name property for callable, and included checks for when serialize functions need to be added --- zend/callable.h | 8 ++++++++ zend/classimpl.cpp | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/zend/callable.h b/zend/callable.h index 95f8e4ed..7b55a283 100644 --- a/zend/callable.h +++ b/zend/callable.h @@ -124,6 +124,14 @@ class Callable */ void initialize(zend_internal_function_info *info, const char *classname = nullptr) const; + /** + * Name of the function + * @return const std::string& + */ + const std::string &name() const + { + return _name; + } protected: diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp index 061eed69..7517caa0 100644 --- a/zend/classimpl.cpp +++ b/zend/classimpl.cpp @@ -8,6 +8,7 @@ */ #include "includes.h" #include +#include /** * Set up namespace @@ -1313,6 +1314,16 @@ const struct _zend_function_entry *ClassImpl::entries() // already initialized? if (_entries) return _entries; + // if the class is serializable + if (_base->serializable()) + { + // we first check if the class already has a registered serialize method + auto result = std::find_if(_methods.begin(), _methods.end(), [](std::shared_ptr method){ return method->name() == "serialize"; }); + if (result == _methods.end()) { /* we need to insert the serialize method ourselves */ } + result = std::find_if(_methods.begin(), _methods.end(), [](std::shared_ptr method){ return method->name() == "unserialize"; }); + if (result == _methods.end()) { /* we need to insert the unserialize method ourselves */ } + } + // allocate memory for the functions _entries = new zend_function_entry[_methods.size() + 1]; From e7d8c1c05ad1450851151b014f13a325782df0f5 Mon Sep 17 00:00:00 2001 From: Bram van den Brink Date: Thu, 23 Jun 2022 11:10:46 +0200 Subject: [PATCH 2/5] made getter oneliner --- zend/callable.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/zend/callable.h b/zend/callable.h index 7b55a283..1334d78a 100644 --- a/zend/callable.h +++ b/zend/callable.h @@ -128,10 +128,7 @@ class Callable * Name of the function * @return const std::string& */ - const std::string &name() const - { - return _name; - } + const std::string &name() const { return _name; } protected: From 1c37c4dbca8c203c48bb44adf96d7ac63f68d5c9 Mon Sep 17 00:00:00 2001 From: Bram van den Brink Date: Fri, 24 Jun 2022 10:52:42 +0200 Subject: [PATCH 3/5] serialize and unserialize methods are now added to the class --- zend/classimpl.cpp | 25 ++++++++++++++++++++----- zend/classimpl.h | 7 +++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp index 7517caa0..fe4994aa 100644 --- a/zend/classimpl.cpp +++ b/zend/classimpl.cpp @@ -1301,6 +1301,19 @@ int ClassImpl::unserialize(zval *object, zend_class_entry *entry, const unsigned return SUCCESS; } +/** + * Helper method to check if a function is registered for this instance + * @param name name of the function to check for + * @return bool Wether the function exists or not + */ +bool ClassImpl::hasMethod(const char* name) const +{ + // find the method + auto result = std::find_if(_methods.begin(), _methods.end(), [name](std::shared_ptr method){ return method->name() == name; }); + // return wether its found or not + return result != _methods.end(); +} + /** * Retrieve an array of zend_function_entry objects that hold the * properties for each method. This method is called at extension @@ -1317,11 +1330,13 @@ const struct _zend_function_entry *ClassImpl::entries() // if the class is serializable if (_base->serializable()) { - // we first check if the class already has a registered serialize method - auto result = std::find_if(_methods.begin(), _methods.end(), [](std::shared_ptr method){ return method->name() == "serialize"; }); - if (result == _methods.end()) { /* we need to insert the serialize method ourselves */ } - result = std::find_if(_methods.begin(), _methods.end(), [](std::shared_ptr method){ return method->name() == "unserialize"; }); - if (result == _methods.end()) { /* we need to insert the unserialize method ourselves */ } + // i register the methods as abstract, though when they are called it seems to call the methods set in + // ClassImpl::Initialize below, so this works to bypass the 7.4 requirement that the functions are defined. + + // add the serialize method if the class does not have one defined yet + if (!hasMethod("serialize")) method("serialize"); + // add the unserialize method if the class does not have one defined yet + if (!hasMethod("unserialize")) method("unserialize", 0, {Php::ByVal("serialized")}); } // allocate memory for the functions diff --git a/zend/classimpl.h b/zend/classimpl.h index 225c3ad7..625e1910 100644 --- a/zend/classimpl.h +++ b/zend/classimpl.h @@ -112,6 +112,13 @@ class ClassImpl */ const zend_function_entry *entries(); + /** + * Helper method to check if a function is registered for this instance + * @param name name of the function to check for + * @return bool Wether the function exists or not + */ + bool hasMethod(const char* name) const; + /** * Helper method to turn a property into a zval * From 8a8bb9ac6f587da76099bb28454832e4123a6b52 Mon Sep 17 00:00:00 2001 From: Bram van den Brink Date: Fri, 24 Jun 2022 10:56:39 +0200 Subject: [PATCH 4/5] removed comments --- zend/classimpl.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp index fe4994aa..edf599a2 100644 --- a/zend/classimpl.cpp +++ b/zend/classimpl.cpp @@ -1330,9 +1330,6 @@ const struct _zend_function_entry *ClassImpl::entries() // if the class is serializable if (_base->serializable()) { - // i register the methods as abstract, though when they are called it seems to call the methods set in - // ClassImpl::Initialize below, so this works to bypass the 7.4 requirement that the functions are defined. - // add the serialize method if the class does not have one defined yet if (!hasMethod("serialize")) method("serialize"); // add the unserialize method if the class does not have one defined yet From 8984634eb383d416ce94f924946999fac1f8d8a1 Mon Sep 17 00:00:00 2001 From: Bram van den Brink Date: Wed, 29 Jun 2022 14:05:15 +0200 Subject: [PATCH 5/5] fixed issue: classes that extend from Serializable incorrectly reported that they were abstract because of missing serialize() and unserialize() methods --- .gitignore | 3 ++- include/base.h | 18 +++++++++++++++++- zend/base.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++++- zend/classimpl.cpp | 25 +++++++++++++++++++----- 4 files changed, 85 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index fe945915..64fc9284 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ *.a.* *.so.* shared/ -static/ \ No newline at end of file +static/ +.vscode/ \ No newline at end of file diff --git a/include/base.h b/include/base.h index 023d7119..e6d8b879 100644 --- a/include/base.h +++ b/include/base.h @@ -2,7 +2,7 @@ * Base class for defining your own objects * * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV + * @copyright 2013 - 2022 Copernica BV */ /** @@ -261,6 +261,22 @@ class PHPCPP_EXPORT Base */ int __compare(const Base &base) const; + /** + * Method that is called when an explicit call to $object->serialize() is made + * Note that a call to serialize($object) does not end up in this function, but + * is handled by the user-space implementation of Serializable::serialize()). + * @return Php::Value + */ + Php::Value __serialize(); + + /** + * Method that is called when an explicit call to $object->unserialize() is made + * Note that a call to unserialize($string) does not end up in this function, but + * is handled by the user-space implementation of Serializable::unserialize()). + * @param params The passed parameters + */ + void __unserialize(Php::Parameters ¶ms); + private: /** diff --git a/zend/base.cpp b/zend/base.cpp index ed6fe3bc..9f000ed8 100644 --- a/zend/base.cpp +++ b/zend/base.cpp @@ -3,7 +3,7 @@ * * Implementation file for the base of all classes * - * @copyright 2014 Copernica BV + * @copyright 2014 - 2022 Copernica BV */ #include "includes.h" @@ -217,6 +217,51 @@ int Base::__compare(const Base &that) const return 1; } +/** + * Method that is called when an explicit call to $object->serialize() is made + * Note that a call to serialize($object) does not end up in this function, but + * is handled by the user-space implementation of Serializable::serialize()). + * @return Php::Value + */ +Php::Value Base::__serialize() +{ + // 'this' refers to a Php::Base class, but we expect that is also implements the Serializable + // interface (otherwise we would never have registered the __serialize function as a callback) + auto *serializable = dynamic_cast(this); + + // this one should not fail + if (serializable == nullptr) return ""; + + // pass the call to the interface + return serializable->serialize(); +} + +/** + * Method that is called when an explicit call to $object->unserialize() is made + * Note that a call to unserialize($string) does not end up in this function, but + * is handled by the user-space implementation of Serializable::unserialize()). + * @param params The passed parameters + */ +void Base::__unserialize(Php::Parameters ¶ms) +{ + // 'this' refers to a Php::Base class, but we expect that is also implements the Serializable + // interface (otherwise we would never have registered the __serialize function as a callback) + auto *serializable = dynamic_cast(this); + + // this one should not fail + if (serializable == nullptr) return; + + // the passed in parameter + Php::Value param = params[0]; + + // make sure the parameter is indeed a string + param.setType(Type::String); + + // pass the call to the interface + serializable->unserialize(param.rawValue(), param.size()); +} + + /** * End namespace */ diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp index edf599a2..dcf91154 100644 --- a/zend/classimpl.cpp +++ b/zend/classimpl.cpp @@ -1327,17 +1327,19 @@ const struct _zend_function_entry *ClassImpl::entries() // already initialized? if (_entries) return _entries; - // if the class is serializable + // the number of entries that need to be allocated + size_t entrycount = _methods.size(); + + // if the class is serializable, we might need some extra methods if (_base->serializable()) { // add the serialize method if the class does not have one defined yet - if (!hasMethod("serialize")) method("serialize"); - // add the unserialize method if the class does not have one defined yet - if (!hasMethod("unserialize")) method("unserialize", 0, {Php::ByVal("serialized")}); + if (!hasMethod("serialize")) entrycount += 1; + if (!hasMethod("unserialize")) entrycount += 1; } // allocate memory for the functions - _entries = new zend_function_entry[_methods.size() + 1]; + _entries = new zend_function_entry[entrycount + 1]; // keep iterator counter int i = 0; @@ -1352,6 +1354,19 @@ const struct _zend_function_entry *ClassImpl::entries() method->initialize(entry, _name); } + // if the class is serializable, we might need some extra methods + if (_base->serializable()) + { + // the method objectneed to stay in scope for the lifetime of the script (because the register a pointer + // to an internal string buffer) -- so we create them as static variables + static Method serialize("serialize", &Base::__serialize, 0, {}); + static Method unserialize("unserialize", &Base::__unserialize, 0, { ByVal("input", Type::Undefined, true) }); + + // register the serialize and unserialize method in case this was not yet done in PHP user space + if (!hasMethod("serialize")) serialize.initialize(&_entries[i++], _name); + if (!hasMethod("unserialize")) unserialize.initialize(&_entries[i++], _name); + } + // last entry should be set to all zeros zend_function_entry *last = &_entries[i];