summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--documentation/constructors-and-destructors.html194
-rw-r--r--include/class.h42
-rw-r--r--include/classbase.h6
-rw-r--r--src/classbase.cpp13
-rw-r--r--src/includes.h1
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 @@
-<h1>Constructors and destructors</h1>
+<h1>Constructors</h1>
<p>
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();
</code></pre>
</p>
<p>
- 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 <i>after</i>
+ the object is constructed.
</p>
<p>
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.
+</p>
+<p>
+ 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.
</p>
<p>
<pre class="language-c++"><code>
#include &lt;phpcpp.h&gt;
-// 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 &params)
{
// 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");
?&gt;
</code></pre>
</p>
+<p>
+ 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().
+</p>
<h2 id="private-constructors">Private constructors</h2>
<p>
Just like any other method, the __construct() method can also be
@@ -148,33 +181,140 @@ echo($counter->value()."\n");
going to fail - and not the actual object construction.
</p>
<p>
+ 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).
+</p>
+<p>
<pre class="language-c++"><code>
#include &lt;phpcpp.h&gt;
+/**
+ * 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&lt;Counter&gt; 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);
...
}
}
</code></pre>
</p>
+<h2 id=cloning">Cloning objects</h2>
+<p>
+ 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.
+</p>
+<p>
+<pre class="language-c++"><code>
+#include &lt;phpcpp.h&gt;
+
+/**
+ * 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 &amp;counter) = delete;
+
+ /**
+ * c++ destructor
+ */
+ virtual ~Counter() {}
+
+ /**
+ * php "constructor"
+ * @param params
+ */
+ void __construct(Php::Parameters &params)
+ {
+ // 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&lt;Counter&gt; 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;
+ }
+}
+</code></pre>
+</p>
<p>
- 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.
</p>
<h2 id="constructing-objects">Constructing objects</h2>
<p>
diff --git a/include/class.h b/include/class.h
index 0bfa745..9fa14a7 100644
--- a/include/class.h
+++ b/include/class.h
@@ -169,17 +169,49 @@ private:
}
/**
+ * Method to clone the object if it is copy constructable
+ * @param orig
+ * @return Base*
+ */
+ template <typename X = T>
+ typename std::enable_if<std::is_copy_constructible<X>::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 X = T>
+ typename std::enable_if<!std::is_copy_constructible<X>::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<T>::value;
+ }
+
+ /**
* Construct a clone
* @param orig
* @return Base
*/
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>((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 <memory>
#include <list>
#include <exception>
+#include <type_traits>
// for debug
#include <iostream>