summaryrefslogtreecommitdiff
path: root/documentation/constructors-and-destructors.html
blob: 019bbe427b821864754461694db4754a811d470b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
<h1>Constructors and destructors</h1>
<p>
    There is a small but very important difference between constructors and 
    destructors in C++, and the __construct() and __destruct() methods in PHP.
</p>
<p>
    A C++ constructor is called on an object that is <i>being</i> initialized,
    but that is <i>not</i> in an initialized state <i>yet</i>. You can experience this by
    calling a pure virtual method from a constructor. This will make your 
    program crash, even when the pure virtual method was implemented in the
    derived class. The reason for this is that inside the C++ constructor the 
    object is not yet fully initialized, and the object is not yet aware of 
    it's position in the class hierarchy. The call to the pure virtual method 
    can thus not be passed on to the derived object.
</p>
<p>
<pre class="language-c++"><code>
#include &lt;iostream&gt;

// define a base class with a pure virtual method that is called from the
// constructor
class BASE 
{
public:
    // constructor
    BASE() 
    {
        // call the pure virtual method
        doSomething();
    }
    
    // define method that should be implemented by derived classes
    virtual void doSomething() = 0;
};

// define a derived class
class DERIVED : public BASE 
{
public:
    // implementation of the virtual function
    virtual void doSomething() override 
    {
        std::cout &lt;&lt; "doSomething()" &lt;&lt; std::endl;
    }
};

// main procedure
int main()
{
    DERIVED d;
    return 0;
}
</code></pre>
</p>
<p>
    The above program crashes (some compilers even refuse to compile this). 
    In PHP however, when the __construct() method 
    gets called, the object is already fully initialized and it is perfectly 
    legal to make calls to abstract methods that are implemented in derived 
    classes, as you can see in the following example.
</p>
<p>
<pre class="language-php"><code>
&lt;?php

// base class in PHP, in which the an abstract method is called
abstract class BASE 
{
    // constructor
    public function __construct() 
    {
        // call abstract method
        $this->doSomething();
    }
    
    // abstract method to be implemented by derived classes
    public abstract function doSomething();
}

// the derived class
class DERIVED extends BASE 
{
    // implement the abstract method
    public function doSomething() 
    {
        echo("doSomething()\n");
    }
}

// create an instance of the derived class
$d = new DERIVED();
?&gt;    
</code></pre>
</p>
<p>
    This PHP script correctly outputs 'doSomething()'. This happens because the 
    __construct() method in PHP does not really construct anything, it 
    has access to all members, and (when available) 
    also the base class and overridden methods. 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 right 
    after the object is constructed, and that is called automatically.
</p>
<p>
    This difference is important for you as a C++ programmer, because you should
    never confuse your C++ constructor with the __construct() method. In the C++
    constructor, the C++ object is being constructed and the
    PHP object does not yet exist. After the constructor is finished, the PHP engine
    will create the PHP object, and the __construct() method gets called. It is therefore
    valid to have both a C++ constructor and a __construct() method in your class.
</p>
<p>
<pre class="language-c++"><code>
#include &lt;phpcpp.h&gt;

// actual class implementation
class Counter : public Php::Base
{
private:
    int _value = 0;

public:
    // c++ constructor
    Counter() {}
    
    // c++ destructor
    virtual ~Counter() {}
    
    // php "constructor"
    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; }
};

extern "C" {
    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);
        
        // add the class to the extension
        myExtension.add(std::move(counter));
        
        // return the extension
        return myExtension;
    }
}
</code></pre>
</p>
<p>
    The code above shows that __construct() is registered as if it was 
    a regular method - and that's what it is. The counter example that we've 
    used before is now extended so that it is possible to give it an initial
    value.
</p>
<p>
<pre class="language-php"><code>
&lt;?php
$counter = new Counter(10);
$counter-&gt;increment();
echo($counter->value()."\n");
?&gt;
</code></pre>
</p>
<h2>Private constructors</h2>
<p>
    Just like any other method, the __construct() method can also be
    marked as being private or protected. If you do this, you will make it
    impossible to create instances of your class from PHP scripts. It is 
    important to realize that the C++ constructor and C++ destructor still get 
    called in such situations, because it is the __construct() call that is 
    going to fail - and not the actual object construction.
</p>
<p>
<pre class="language-c++"><code>
#include &lt;phpcpp.h&gt;

extern "C" {
    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!
        counter.method("__construct", &Counter::__construct, Php::Private);
        counter.method("__clone", &Counter::__construct, Php::Private);
        
        ...
    }
}
</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.
</p>
<h2>Constructing objects</h2>
<p>
    The Php::Value class can be used as a regular PHP $variable, and you can therefore
    also use it for storing object instances. But how do you create brand
    new objects? For this we have the Php::Object class - which is simply an
    overridden Php::Value class with alternative constructors, and some additional 
    checks to prevent that you will ever use a Php::Object class to store values
    other than objects.
</p>
<p>
<pre class="language-c++"><code>
// new variable holding the string "Counter"
Php::Value counter0("Counter");

// new variable holding a newly created object of type "Counter",
// the __construct() gets called without parameters
Php::Object counter1("Counter");

// new variable holding a newly created object, and 
// the __construct() is being called with value 10
Php::Object counter2("Counter", 10);

// new builtin DateTime object, constructed with "now"
Php::Object time("DateTime", "now");

// valid, a Php::Object is an extended Php::Value, and 
// can thus be assigned to a base Php::Value object
Php::Value copy1 = counter1;

// invalid statement, a Php::Object can only be used for storing objects
Php::Object copy2 = counter0;
</code></pre>
</p>
<p>
    The constructor of a Php::Object takes the name of a class, and an optional
    list of parameters that will be passed to the __construct() function. You 
    can use names from builtin PHP classes and other extensions (like DateTime), 
    classes from your extension (like Counter), and even classes from PHP user 
    space.
</p>
<p>
    The Php::Object class can also be used if you want to construct an instance
    of your own C++ class without calling the __construct() function. This can 
    for example be useful when the __construct() method is private, or when you
    want to bypass a call to your own __construct() method.
</p>
<p>
<pre class="language-c++"><code>
#include &lt;phpcpp.h&gt;

// actual class implementation
class Counter : public Php::Base
{
private:
    int _value = 0;

public:
    // c++ constructor
    Counter(int value) : _value(value) {}
    
    // c++ destructor
    virtual ~Counter() {}
    
    // php "constructor"
    void __construct() {}

    // functions to increment and decrement
    Php::Value value() const { return _value; }
};

// function to create a new timer
Php::Value createTimer()
{
    return Php::Object("Counter", new Counter(100));
}

extern "C" {
    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,
        // the __construct method is private because PHP scripts are not allowed
        // to create Counter instances
        Php::Class&lt;Counter&gt; counter("Counter");
        counter.method("__construct", &Counter::__construct, Php::Private);
        counter.method("value", &Counter::value);
        
        // add the class to the extension
        myExtension.add(std::move(counter));
        
        // add the factory function to create a timer to the extension
        myExtension.add("createTimer", createTimer);
        
        // return the extension
        return myExtension;
    }
}
</code></pre>
</p>
<p>
    In the code above we made the __construct() function of the Counter class
    private. This makes it impossible to create instances of this class - both
    from PHP user scripts, and via calls to Php::Object("Counter") - because
    constructing objects in these ways will eventually result in a forbidden 
    __construct() call.
</p>
<p>
    The Php::Object does have an alternative syntax that takes a pointer 
    to a C++ class (allocated on the heap, with operator new!) and that turns 
    this pointer into a PHP variable without calling the 
    __construct() method. Notice that you must also specify the classname, 
    because C++ classes do not have any information about themselves (like their 
    name), while in PHP such information is required to handle reflection and 
    functions like get_class().
</p>
<h2>Other magic methods</h2>
<p>
    The __construct() and __destruct() methods are essentially regular methods
    that get automatically called by PHP in certain situations. The same is true
    for other magic methods like __toString(), __get(), __set(), et cetera. You
    can implement these methods in the same was as you would do for other methods.
    Let's add a __toString() method to our Counter class:
</p>
<p>
<pre class="language-c++"><code>
#include &lt;phpcpp.h&gt;

// actual class implementation
class Counter : public Php::Base
{
private:
    int _value = 0;

public:
    // c++ constructor
    Counter() {}
    
    // c++ destructor
    virtual ~Counter() {}
    
    // functions to increment and decrement
    Php::Value increment() { return ++_value; }
    Php::Value decrement() { return --_value; }
    Php::Value value() const { return _value; }
    
    // convert to string
    Php::Value toString() const { return std::to_string(_value); }
    
};

extern "C" {
    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);
        counter.method("__toString", &Counter::toString);
        
        // add the class to the extension
        myExtension.add(std::move(counter));
        
        // return the extension
        return myExtension;
    }
}
</code></pre>
</p>
<p>
    You can also see that it is not necessary to use the same method names
    in the C++ class as in PHP. The C++ method "toString" was used, and mapped
    to the PHP function __toString().
</p>