From 2381b65c0bf4fc8a78e9040ffb4a1674bab4e2ad Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 13 Mar 2014 23:35:54 +0100 Subject: classes without a copy constructor can now also be used from PHP, and they automatically become unclonable --- documentation/constructors-and-destructors.html | 194 ++++++++++++++++++++---- include/class.h | 42 ++++- include/classbase.h | 6 +- src/classbase.cpp | 13 +- src/includes.h | 1 + 5 files changed, 217 insertions(+), 39 deletions(-) diff --git a/documentation/constructors-and-destructors.html b/documentation/constructors-and-destructors.html index 110ed60..b5bdeb0 100644 --- a/documentation/constructors-and-destructors.html +++ b/documentation/constructors-and-destructors.html @@ -1,4 +1,4 @@ -

Constructors and destructors

+

Constructors

There is a small but very important difference between constructors and destructors in C++, and the __construct() and __destruct() methods in PHP. @@ -55,54 +55,81 @@ $d = new DERIVED();

- This script outputs 'doSomething()'. In fact, __construct() is not a - constructor at all, but a very normal method that just happens to be the - first method that is called, and that is called automatically after the - object is constructed. + This script outputs 'doSomething()'. The reason for this is that __construct() + is not a constructor at all, but a very normal method that just happens to + be the first method that is called, and that is called automatically after + the object is constructed.

This difference is important for you as a C++ programmer, because you should never confuse your C++ constructor with the PHP __construct() method. In the C++ - constructor, the object is being constructed and the PHP object does not - yet exist. After the constructor is finished, the PHP engine - takes over control and creates the PHP object, and the PHP-CPP library then - links that PHP object to your C++ object. Only after both the PHP object and - the C++ object are fully constructed, the __construct() method is called - just - like a normal method. It is therefore not uncommon to have both a C++ constructor - and a __construct() method in your class. The C++ constructor to initialize - the member variables, and __construct() to activate the object. + constructor the object is being constructed and not all data is yet available. + Virtual methods can not be called, and the object also does not yet exist in + PHP user space. +

+

+ After the constructor is finished, the PHP engine takes over control and creates + the PHP object, and the PHP-CPP library then links that PHP object to your C++ + object. Only after both the PHP object and the C++ object are fully constructed, + the __construct() method is called - just like a normal method. It is therefore + not uncommon to have both a C++ constructor and a __construct() method in your + class. The C++ constructor to initialize the member variables, and __construct() + to activate the object.


 #include <phpcpp.h>
 
-// actual class implementation
+/**
+ *  Simple counter class
+ */
 class Counter : public Php::Base
 {
 private:
+    /**
+     *  Internal value
+     *  @var int
+     */
     int _value = 0;
 
 public:
-    // c++ constructor
+    /**
+     *  c++ constructor
+     */
     Counter() {}
     
-    // c++ destructor
+    /**
+     *  c++ destructor
+     */
     virtual ~Counter() {}
     
-    // php "constructor"
+    /**
+     *  php "constructor"
+     *  @param  params
+     */
     void __construct(Php::Parameters ¶ms)
     {
         // copy first parameter (if available)
         if (params.size() > 0) _value = params[0];
     }
 
-    // functions to increment and decrement
+    /**
+     *  functions to increment and decrement
+     */
     Php::Value increment() { return ++_value; }
     Php::Value decrement() { return --_value; }
     Php::Value value() const { return _value; }
 };
 
+/**
+ *  Switch to C context so that the get_module() function can be
+ *  called by C programs (which the Zend engine is)
+ */
 extern "C" {
+    /**
+     *  Startup function for the extension
+     *  @return void*
+     */
     PHPCPP_EXPORT void *get_module() {
         static Php::Extension myExtension("my_extension", "1.0");
         
@@ -138,6 +165,12 @@ echo($counter->value()."\n");
 ?>
 

+

+ Because the __construct() method is seen as a regular method, you can also + specify its parameters, and whether the method is public, private or protected. + The __construct() is also directly callable from PHP user space, so that + derived methods can explicitly call parent::__construct(). +

Private constructors

Just like any other method, the __construct() method can also be @@ -147,34 +180,141 @@ echo($counter->value()."\n"); called in such situations, because it is the __construct() call that is going to fail - and not the actual object construction.

+

+ Yes indeed: if you make the __construct() method private, and inside a PHP + script a "new Counter()" call is executed, the PHP-CPP library will first + instantiate a new instance of your class, then report an error because the + __construct() method is private, and then immediately destruct the object + (and call the C++ destructor). +


 #include <phpcpp.h>
 
+/**
+ *  Switch to C context so that the get_module() function can be
+ *  called by C programs (which the Zend engine is)
+ */
 extern "C" {
+    /**
+     *  Start point of the extension
+     *  @return void*
+     */
     PHPCPP_EXPORT void *get_module() {
         static Php::Extension myExtension("my_extension", "1.0");
         
         // description of the class so that PHP knows which methods are accessible
         Php::Class<Counter> counter("Counter");
         
-        // add a private __construct and __clone method to the class, so that
-        // objects can not be constructed or cloned from PHP scripts. Be aware
-        // that the C++ constructer does get called - it will be the call to 
-        // the first __construct() function that will fail!
+        // add a private __construct method to the class, so that objects can 
+        // not be constructed from PHP scripts. Be aware that the C++ constructer 
+        // does get called - it will be the call to the first __construct() 
+        // function that will fail, and not the actual object construction.
         counter.method("__construct", &Counter::__construct, Php::Private);
-        counter.method("__clone", &Counter::__construct, Php::Private);
         
         ...
     }
 }
 

+

Cloning objects

+

+ If your class has a copy constructor, it automatically becomes clonable. If + you do not want that your class can be cloned by PHP scripts, you can do + two things: you can either remove the copy constructor from your class, or + you can register a private __clone() method, just like we registered a + private __construct() method before. +

+

+


+#include <phpcpp.h>
+
+/**
+ *  Simple counter class
+ */
+class Counter : public Php::Base
+{
+private:
+    /**
+     *  Internal value
+     *  @var int
+     */
+    int _value = 0;
+
+public:
+    /**
+     *  c++ constructor
+     */
+    Counter() {}
+    
+    /**
+     *  Remove the copy constructor
+     *  
+     *  By removing the copy constructor, the PHP clone operator will
+     *  automatically be deactivated. PHP will trigger an error if 
+     *  an object is attempted to be cloned.
+     *  
+     *  @param  counter
+     */
+    Counter(const Counter &counter) = delete;
+    
+    /**
+     *  c++ destructor
+     */
+    virtual ~Counter() {}
+    
+    /**
+     *  php "constructor"
+     *  @param  params
+     */
+    void __construct(Php::Parameters ¶ms)
+    {
+        // copy first parameter (if available)
+        if (params.size() > 0) _value = params[0];
+    }
+
+    /**
+     *  functions to increment and decrement
+     */
+    Php::Value increment() { return ++_value; }
+    Php::Value decrement() { return --_value; }
+    Php::Value value() const { return _value; }
+};
+
+/**
+ *  Switch to C context so that the get_module() function can be
+ *  called by C programs (which the Zend engine is)
+ */
+extern "C" {
+    /**
+     *  Startup function for the extension
+     *  @return void*
+     */
+    PHPCPP_EXPORT void *get_module() {
+        static Php::Extension myExtension("my_extension", "1.0");
+        
+        // description of the class so that PHP knows which methods are accessible
+        Php::Class<Counter> counter("Counter");
+        counter.method("__construct", &Counter::__construct);
+        counter.method("increment", &Counter::increment);
+        counter.method("decrement", &Counter::decrement);
+        counter.method("value", &Counter::value);
+        
+        // alternative way to make an object unclonable
+        counter.method("__clone", Php::Private);
+        
+        // add the class to the extension
+        myExtension.add(std::move(counter));
+        
+        // return the extension
+        return myExtension;
+    }
+}
+
+

- The same happens when you add a private __clone() method. It will then not - be possible to clone the object from PHP code, although your C++ class still - needs a copy constructor, which is called when a "clone $object" instruction - is given in a PHP script. + In the above example we have shown both ways to make object unclonable. + Using only one of them is already sufficient.

Constructing objects

diff --git a/include/class.h b/include/class.h index 0bfa745..9fa14a7 100644 --- a/include/class.h +++ b/include/class.h @@ -168,6 +168,41 @@ private: return new T(); } + /** + * Method to clone the object if it is copy constructable + * @param orig + * @return Base* + */ + template + typename std::enable_if::value, Base*>::type + static maybeClone(X *orig) + { + // create a new instance + return new X(*orig); + } + + /** + * Method to clone the object if it is copy constructable + * @param orig + * @return Base* + */ + template + typename std::enable_if::value, Base*>::type + static maybeClone(X *orig) + { + // impossible return null + return nullptr; + } + + /** + * Is this a clonable class? + * @return bool + */ + virtual bool clonable() const + { + return std::is_copy_constructible::value; + } + /** * Construct a clone * @param orig @@ -175,11 +210,8 @@ private: */ virtual Base *clone(Base *orig) const override { - // cast to the original object - T *t = (T *)orig; - - // construct a new base by calling the copy constructor - return new T(*t); + // maybe clone it (if the class has a copy constructor) + return maybeClone((T*)orig); } /** diff --git a/include/classbase.h b/include/classbase.h index 77f7948..0ea6ea4 100644 --- a/include/classbase.h +++ b/include/classbase.h @@ -133,11 +133,13 @@ protected: virtual Base *clone(Base *orig) const { return nullptr; } /** - * Methods to check if a certain interface is overridden + * Methods to check if a certain interface is overridden, or a copy + * constructor is available * @return bool */ virtual bool traversable() const { return false; } virtual bool serializable() const { return false; } + virtual bool clonable() const { return false; } /** * Compare two objects @@ -341,7 +343,7 @@ private: * Retrieve pointer to our own object handlers * @return zend_object_handlers */ - static struct _zend_object_handlers *objectHandlers(); + struct _zend_object_handlers *objectHandlers(); /** * Function to create a new iterator to iterate over an object diff --git a/src/classbase.cpp b/src/classbase.cpp index 0a07566..ec316cd 100644 --- a/src/classbase.cpp +++ b/src/classbase.cpp @@ -363,7 +363,7 @@ zend_object_handlers *ClassBase::objectHandlers() memcpy(&handlers, &std_object_handlers, sizeof(zend_object_handlers)); // install custom clone function - handlers.clone_obj = &ClassBase::cloneObject; + handlers.clone_obj = clonable() ? &ClassBase::cloneObject : nullptr; // functions for the Countable interface handlers.count_elements = &ClassBase::countElements; @@ -534,14 +534,16 @@ zend_object_value ClassBase::cloneObject(zval *val TSRMLS_DC) // create a new base c++ object auto *cpp = meta->clone(old_object->cpp); - // report error on failure + // report error on failure (this does not occur because the cloneObject() + // method is only installed as handler when we have seen that there is indeed + // a copy constructor) if (!cpp) throw Php::Exception(std::string("Unable to clone ") + entry->name); // the thing we're going to return zend_object_value result; // set the handlers - result.handlers = ClassBase::objectHandlers(); + result.handlers = meta->objectHandlers(); // store the object MixedObject *new_object = cpp->store(entry); @@ -549,7 +551,8 @@ zend_object_value ClassBase::cloneObject(zval *val TSRMLS_DC) // store the object in the object cache result.handle = cpp->handle(); - // clone the members + // clone the members (this will also call the __clone() function if the user + // had registered that as a visible method) zend_objects_clone_members(&new_object->php, result, &old_object->php, Z_OBJ_HANDLE_P(val)); // done @@ -1094,7 +1097,7 @@ zend_object_value ClassBase::createObject(zend_class_entry *entry TSRMLS_DC) zend_object_value result; // set the handlers - result.handlers = ClassBase::objectHandlers(); + result.handlers = meta->objectHandlers(); // store the object cpp->store(entry); diff --git a/src/includes.h b/src/includes.h index 0dd027c..50d4144 100644 --- a/src/includes.h +++ b/src/includes.h @@ -18,6 +18,7 @@ #include #include #include +#include // for debug #include -- cgit v1.2.3