summaryrefslogtreecommitdiff
path: root/documentation/magic-methods-and-interfaces.html
blob: 6ece6a56641b9371d48f84f9e3988535f97fefdc (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
<h1>Magic methods and interfaces</h1>
<p>
    PHP classes have a number of magic methods that you can implement to 
    enable special features. These are methods like __toString(), __get(), __set(), 
    __invoke(), etcetera. With the PHP-CPP library you can implement these 
    magic methods too.
</p>
<p>
    Besides that, a core PHP installation also comes with a number of interfaces
    that you can implement to add even more special features to a class. These
    are interfaces with names like 'Countable', 'ArrayAccess' and 'Serializable'.
    The features that these interfaces bring, can also be implemented using
    PHP-CPP.
</p>
<p>
    So there are magic methods and interfaces. Strangely enough,
    there does not seem to be any uniformity in the Zend engine in the choice 
    between these magic methods and interfaces. To us it is unclear why some special features 
    are implemented with magic methods, while others are activated by implementing
    interfaces. In our eyes the Serializable interface could just as well have 
    been implemented with magic __serialize() and __unserialize() methods, or the
    __invoke() method could just as well have been an "Invokable" interface. 
    PHP is not a standardized language, and some things just seem to be the way they
    are because someone felt like implementing it this way or another...
</p>
<p>
    The PHP-CPP library tries to stay as close to PHP as possible. That's why in
    your C++ classes you can also override the magic methods and implement the
    special interfaces - and because C++ does not have interfaces like PHP has,
    we use classes with pure virtual methods instead.
</p>
<h2>Support for the SPL</h2>
<p>
    A standard PHP installation comes with the Standard PHP Library (SPL). This
    is an extension that is built on top of the Zend engine and that uses features
    from the Zend engine to create classes and interfaces like Countable, Iterator
    and ArrayAccess.
</p>
<p>
    The PHP-CPP library also has interfaces with these names, and they behave in
    more or less the same way as the SPL interfaces. But internally, the PHP-CPP
    does not depend on the SPL. If you implement a C++ interface like 
    Php::ArrayAccess or Php::Countable, it is something different than writing
    a class in PHP that implements a SPL interface.
</p>
<p>
    Both PHP-CPP and the SPL are directly built on top of the Zend core and 
    offer the same sort of features, but they do not rely on each other. You can 
    thus safely use PHP-CPP if you have not loaded the SPL extension.
</p>
<h2>The Countable interface</h2>
<p>
    By implementing the Php::Countable interface, you can create objects that
    can be passed to the PHP count() function. You have to implement a count()
    method that returns the value that you want the count() function to return.
</p>
<p>
<pre class="language-c++"><code>
#include &lt;phpcpp.h&gt;

/**
 *  The famous counter class, now also implements 
 *  the Php::Countable interface
 */
class Counter : public Php::Base, public Php::Countable
{
private:
    /**
     *  The internal counter value
     *  @var int
     */
    int _value = 0;

public:
    /**
     *  C++ constructor and C++ destructpr
     */
    Counter() {}
    virtual ~Counter() {}
    
    /**
     *  Methods to increment and decrement the counter
     */
    Php::Value increment() { return ++_value; }
    Php::Value decrement() { return --_value; }

    /**
     *  Method from the Php::Countable interface, that
     *  is used when a Counter instance is passed to the
     *  PHP count() function
     *  
     *  @return long
     */
    virtual long count() override { return _value; }
};

/**
 *  Switch to C context to ensure that the get_module() function
 *  is callable by C programs (which the Zend engine is)
 */
extern "C" {
    /**
     *  Startup function that is called by the Zend engine 
     *  to retrieve all information about the extension
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() {
        
        // extension object
        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 methods
        counter.method("increment", &Counter::increment);
        counter.method("decrement", &Counter::decrement);
        
        // add the class to the extension
        myExtension.add(std::move(counter));
        
        // return the extension
        return myExtension;
    }
}
</code></pre>
</p>
<p>
    The Counter class that we used before has been modified to
    show how to make classes that implement the Php::Countable interface.
    It is very simple, all you have to to is add the Php::Countable class
    as base class. This Php::Countable class has one pure virtual method,
    count(), that has to be implemented.
</p>
<p>
    And that's is all that you have to do. There is no need to register the
    special count() function inside the get_module() function, adding
    Php::Countable as base class is sufficient.
</p>
<p>
<pre class="language-php"><code>
&lt;?php
// create a counter
$counter = new Counter();
$counter->increment();
$counter->increment();
$counter->increment();

// show the current value
echo(count($counter)."\n");
?&gt;
</pre></code>
</p>
<p>
    The output is, as expected, the value 3.
</p>
<h2>The ArrayAccess interface</h2>
<p>
    A PHP object can be turned into a variable that behaves like an array by
    implementing the Php::ArrayAccess interface. When you do this, objects
    can be accessed with array access operators ($object["property"]).
</p>
<p>
    In the following example we use the Php::Countable and the Php::ArrayAccess
    interfaces to create an associative array class than can
    be used for storing strings (remember: this is just an example, PHP already
    has support for associative arrays, so it is debatable how useful the
    example is).
</p>
<p>
<pre class="language-c++"><code>
#include &lt;phpcpp.h&gt;

/**
 *  A sample Map class, that can be used to map string-to-strings
 */
class Map : public Php::Base, public Php::Countable, public Php::ArrayAccess
{
private:
    /**
     *  Internally, a C++ map is used
     *  @var    std::map&lt;std::string,std::string&gt;
     */
    std::map&lt;std::string,std::string&gt; _map;

public:
    /**
     *  C++ constructor and C++ destructpr
     */
    Map() {}
    virtual ~Map() {}
    
    /**
     *  Method from the Php::Countable interface that 
     *  returns the number of elements in the map
     *  @return long
     */
    virtual long count() override 
    { 
        return _map.size(); 
    }

    /**
     *  Method from the Php::ArrayAccess interface that is
     *  called to check if a certain key exists in the map
     *  @param  key
     *  @return bool
     */
    virtual bool offsetExists(const Php::Value &key) override
    {
        return _map.find(key) != _map.end();
    }
    
    /**
     *  Set a member
     *  @param  key
     *  @param  value
     */
    virtual void offsetSet(const Php::Value &key, const Php::Value &value) override
    {
        _map[key] = value;
    }
    
    /**
     *  Retrieve a member
     *  @param  key
     *  @return value
     */
    virtual Php::Value offsetGet(const Php::Value &key) override
    {
        return _map[key];
    }
    
    /**
     *  Remove a member
     *  @param key
     */
    virtual void offsetUnset(const Php::Value &key) override
    {
        _map.erase(key);
    }
};

/**
 *  Switch to C context to ensure that the get_module() function
 *  is callable by C programs (which the Zend engine is)
 */
extern "C" {
    /**
     *  Startup function that is called by the Zend engine 
     *  to retrieve all information about the extension
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() {
        
        // extension object
        static Php::Extension myExtension("my_extension", "1.0");
        
        // description of the class so that PHP knows 
        // which methods are accessible
        Php::Class&lt;Map&gt; map("Map");
        
        // add the class to the extension
        myExtension.add(std::move(map));
        
        // return the extension
        return myExtension;
    }
}
</code></pre>
</p>
<p>
    The Php::ArrayAccess has four pure virtual methods that have to be 
    implemented. These are methods to retrieve and overwrite an element,
    to check if an element with a certain key exists, and a method to
    remove an element. In the example these methods have all been implemented
    to be forwarded to a regular C++ std::map object.
</p>
<p>
    Inside the get_module() function, the Map is registered and added to the
    extension. But unlike many other examples, none of the class methods are
    exported to PHP. It only implements the Php::Countable interface and 
    Php::ArrayAccess interface, so it is perfectly usable to store and retrieve 
    properties, but from a PHP script it does not have any callable methods. 
    The following script shows how to use it.
</p>
<p>
<pre class="language-php"><code>
&lt;?php
// create a map
$map = new Map();

// store some values
$map["a"] = 1234;
$map["b"] = "xyz";
$map["c"] = 0;

// show the values
echo($map["a"]."\n");
echo($map["b"]."\n");
echo($map["c"]."\n");

// access a value that does not exist
echo($map["d"]."\n");

?&gt;
</pre></code>
</p>
<p>
    The output speaks for itself. The map has three members, "1234" (a string
    variable), "xyz" and "0".
</p>