summaryrefslogtreecommitdiff
path: root/documentation/magic-interfaces.html
blob: 83494c48955d48fbc3642becde6af3e9c1750f88 (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
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
<h1>Magic interfaces</h1>
<p>
    A core PHP installation comes with a number of special "magic" PHP interfaces
    that script writers can implement to add 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>
    You may wonder why PHP sometimes uses <a href="magic-methods">magic methods</a> 
    (for example __set and __unset) and sometimes uses interfaces to change the 
    behavior of a class. There does not seem to be any uniformity in this choice. 
    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>
    Nevertheless, the PHP-CPP library tries to stay as close to PHP as possible. That's why in
    your C++ classes you can also use the special interfaces - and because C++ 
    does not have interfaces like PHP has, classes with pure virtual methods are
    used instead.
</p>
<h2 id="support-for-spl">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
    library 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 id="countable">The Countable interface</h2>
<p>
    By implementing the Php::Countable interface, you can create objects that
    can be passed to the PHP count() function.
</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++ destructor
     */
    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 id="arrayaccess">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.stringValue();
    }
    
    /**
     *  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");

// this will result in a fatal error,
// the ArrayAccess methods are not exported to user space
echo($map->offsetGet("a")."\n");

?&gt;
</pre></code>
</p>
<p>
    The output speaks for itself. The map has three members, "1234" (a string
    variable), "xyz" and "0".
</p>
<h2 id="traversable">The Traversable interface</h2>
<p>
    Classes can also be used in foreach loops, just like regular arrays. If you
    want to enable this feature, your class should extend from the Php::Traverable 
    base class and implement the getIterator() method.
</p>
<p>
<pre class="language-php"><code>
&lt;?php
// fill a map
$map = new Map();
$map["a"] = 1234;
$map["b"] = 5678;

// iterate over it
foreach ($map as $key => $value)
{
    // output the key and value
    echo("$key: $value\n");
}
?&gt;
</code></pre>
</p>
<p>
    The PHP-CPP library implements iterators in a slightly different manner than
    the SPL does, and that you are used to if you have been working with PHP.
    In PHP, to make a class traversable (usable in foreach loops), you have to
    implement either the Iterator interface, or the IteratorAggregate interface.
    This is a peculiar architecture - if not to say faulty. When you think about it,
    it is not the container object itself that is the iterator, that container object is 
    only iterat<i>able</i>! It is <i>being iterated over</i>. In our above example, 
    the $map variable is not the actual iterator, but the container that is 
    iterated over. The real iterator is a hidden object that is not exposed to 
    your PHP script and that controls the foreach loop. The SPL however, would call
    the map an iterator too.
</p>
<p>
    In PHP-CPP we have therefore decided not to follow the SPL API,
    and create a completely new way of implementing traversable classes.
    To make a class traversable, it must be extended from the Php::Traversable
    base class, that forces you to implement the getIterator() method. This
    method should return a Php::Iterator instance.
</p>
<p>
    The Php::Iterator object has five methods that are needed for
    running the foreach loop. Note that your Iterator class does not have to 
    be a class that is accessible from PHP, and does not have to be derived from
    Php::Base. It is an internal class that is used by foreach loops, but that
    does not (have to) exist in PHP user space.
</p>
<p>
<pre class="language-c++"><code>
#include &lt;phpcpp.h&gt;

/**
 *  A sample iterator class that can be used to iterate
 *  over a map of strings
 */
class MapIterator : public Php::Iterator
{
private:
    /**
     *  The map that is being iterated over
     *  This is a reference to the actual map
     *  @var    std::map&lt;std::string,std::string&gt;
     */
    const std::map&lt;std::string,std::string&gt; &_map;
    
    /**
     *  The actual C++ iterator
     *  @var    std::map&lt;std::string,std::string&gtl;::const_iterator;
     */
    std::map&lt;std::string,std::string&gt;::const_iterator _iter;

public:
    /**
     *  Constructor
     *  @param  object      The object that is being iterated over
     *  @param  map         The internal C++ map that is being iterated over
     */
    MapIterator(Map *object, const std::map&lt;std::string,std::string&gt; &map) :
        Php::Iterator(object), _map(map), _iter(map.begin()) {}
        
    /**
     *  Destructor
     */
    virtual ~MapIterator() {}
    
    /**
     *  Is the iterator on a valid position
     *  @return bool
     */
    virtual bool valid() override
    {
        return _iter != _map.end();
    }
    
    /**
     *  The value at the current position
     *  @return Value
     */
    virtual Php::Value current() override
    {
        return _iter->second;
    }
    
    /**
     *  The key at the current position
     *  @return Value
     */
    virtual Php::Value key() override
    {
        return _iter->first;
    }
    
    /**
     *  Move to the next position
     */
    virtual void next() override
    {
        _iter++;
    }
    
    /**
     *  Rewind the iterator to the front position
     */
    virtual void rewind() override
    {
        _iter = _map.begin();
    }
};


/**
 *  A sample Map class, that can be used to map string-to-strings
 */
class Map : 
    public Php::Base, 
    public Php::Countable, 
    public Php::ArrayAccess,
    public Php::Traversable
{
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.stringValue();
    }
    
    /**
     *  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);
    }
    
    /**
     *  Get the iterator
     *  @return Php::Iterator
     */
    virtual Php::Iterator *getIterator() override
    {
        // construct a new map iterator on the heap
        // the (PHP-CPP library will delete it when ready)
        return new MapIterator(this, _map);
    }
};

/**
 *  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 above example further extends the Map class. It now implements 
    Php::Countable, Php::ArrayAccess and Php::Traversable. This means that
    Map objects can now also be used inside foreach loops to iterate over the
    properties.
</p>
<p>
    For this to work, we had to add the Php::Traversable class as base class
    to the Map class, and implement the getIterator() method. This method
    returns a new MapIterator class, which is allocated on the heap. Don't
    worry about memory management: the PHP-CPP library will destruct your
    iterator the moment the foreach loop is finished.
</p>
<p>
    The MapIterator class is derived from the Php::Iterator class, and
    implements the five methods that are needed for running foreach 
    loops (current(), key(), next(), rewind() and valid()). Note that the
    base Php::Iterator class expects that the object over which it iterates
    is passed to the constructor. This is required so that the iterator object
    can ensure that this iterated object stays in scope for as long as the
    iterator exists.
</p>
<p>
    Our MapIterator implementation internally is just a small wrapper around
    a C++ iterator class. It is of course up to you to create more complex
    iterators when needed.
</p>
<h2 id="serializable">The Serializable interface</h2>
<p>
    By implementing the interface "Php::Serializable" you can install custom
    serialize and unserialize handlers for a class. The built-in PHP 
    serialize() function - you probably already know - is a function that
    can turn arrays or objects (and even nested data structures full of
    of arrays and objects) into simple strings. The unserialize() method does 
    exactly the opposite, and turns such a string back into the original
    data structure.
</p>
<p>
    The default serialize implementation of a class takes all publicly visible 
    properties of an object, and concatenates them into a string. But because 
    your class has a native implementation, and probably no public
    properties, you may want to install a custom serialize handler. In this
    handler you can then store the native object members.
</p>
<p>
<pre class="language-c++"><code>
#include &lt;phpcpp.h&gt;

/**
 *  Counter class that can be used for counting
 */
class Counter : public Php::Base, public Php::Serializable
{
private:
    /**
     *  The initial value
     *  @var    int
     */
    int _value = 0;

public:
    /**
     *  C++ constructor and destructor
     */
    Counter() {}
    virtual ~Counter() {}
    
    /**
     *  Update methods to increment or decrement the counter
     *  Both methods return the NEW value of the counter
     *  @return int
     */
    Php::Value increment() { return ++_value; }
    Php::Value decrement() { return --_value; }
    
    /**
     *  Method to retrieve the current counter value
     *  @return int
     */
    Php::Value value() const { return _value; }
    
    /**
     *  Serialize the object into a string
     *  @return std::string
     */
    virtual std::string serialize() override
    {
        return std::to_string(_value);
    }
    
    /**
     *  Unserialize the object from a string
     *  @param  buffer
     *  @param  size
     */
    virtual void unserialize(const char *buffer, size_t size) override
    {
        _value = ::atoi(buffer);
    }
};

/**
 *  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() {
        // create static instance of the 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");
        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 above example takes the Counter example that you've seen before, and 
    turns it into a serializable object. The Php::Serializable has two pure
    virtual methods that should be added to your class. The serialize() method
    is called to turn the object into a string, and the unserialize() method
    is called on an <i>uninitialized object</i> to revive it from a serialized
    string. Note that if an object is being revived using unserialize(), the
    __construct() method will not be called!
</p>
<p>
<pre class="language-php"><code>
&lt;?php
// create an empty counter and increment it a few times
$counter = new Counter();
$counter->increment();
$counter->increment();

// turn the counter into a storable string
$serializedCounter = serialize($counter);

// revive the counter back into an object
$revivedCounter = unserialize($serializedCounter);

// show the counter value
echo($revivedCounter->value()."\n");
?&gt;
</code></pre>
</p>
<p>
    Does anyone know what the output is? It's 2.    
</p>