summaryrefslogtreecommitdiff
path: root/documentation/extension-callbacks.html
blob: defa550a66cef25f8cdceaf1c601ddafad3ca5d0 (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
<h1>Extension callbacks</h1>
<p>
    As we <a href="loading-extension">explained before</a>, the get_module()
    function is called when your extension is started. It returns a memory address 
    where the Zend engine can find all relevant information about your extension.
</p>
<p>
    After this get_module() call, your extension is loaded and will be used to handle
    <i>multiple</i> pageviews. This is an important difference between standard
    PHP scripts and native extensions, because standard PHP scripts handle only a single
    pageview. But extensions serve multiple pageviews after each other.
</p>
<p>
    This difference is especially important if you use global C++ variables. 
    Such global variables are initialized when the extension is loaded - and not at 
    the beginning of each pageview. Changes that you make to global variables keep 
    their value, and subsequent requests will therefore see the updated values. 
</p>
<p>
    This, by the way, only happens to <i>native</i> variables. Global PHP variables,
    stored in the Php::GLOBALS object, are re-initialized at the beginning of 
    each request. You do not have to worry about changes that you make to 
    global PHP variables: at the beginning of the next request, the Php::GLOBALS
    object is fresh and new, and the changes that you made during the
    previous request are no longer visible.
</p>
<p>
    Back to the global C++ variables. If you want to reset a global variable
    at the beginning of a new request, you can register a special callback function 
    that gets called in front of each request.
</p>
<p>
<pre class="language-c++"><code>#include &lt;phpcpp.h&gt;

/**
 *  Global variable that stores the number of times 
 *  the function updateCounters() has been called in total
 *  @var    int
 */
int invokeTotalCount = 0;

/**
 *  Global variable that keeps track how many times the
 *  function updateCounters() was called during the
 *  current request
 *  @var    int
 */
int invokeDuringRequestCount = 0;

/**
 *  Native function that is callable from PHP
 *
 *  This function updates a number of global variables that count
 *  the number of times a function was called
 */
void updateCounters()
{
    // increment global counters
    invokeTotalCount++;
    invokeDuringRequestCount++;
}

/**
 *  Switch to C context, because the Zend engine expects get get_module()
 *  to have a C style function signature
 */
extern "C" {
    /**
     *  Startup function that is automatically called by the Zend engine
     *  when PHP starts, and that should return the extension details
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() 
    {
        // the extension object
        static Php::Extension extension("my_extension", "1.0");
        
        // install a callback that is called at the beginning 
        // of each request
        extension.onRequest([]() {
            
            // re-initialize the counter
            invokeDuringRequestCount = 0;
        });
        
        // add the updateCounter method to the extension
        extension.add("updateCounters", updateCounters);
        
        // return the extension details
        return extension;
    }
}</code></pre>
</p>
<p>
    The Php::Extension class has a method onRequest() that is used in the
    above example to register a callback function. This callback is called right 
    before every pageview/request. And as you can see, it is 
    permitted to use C++ lambda functions.
</p>
<p>
    The onRequest() is not the only method in the Php::Extension object to
    register a callback. There are in fact
    four different on*() methods that you can use.
</p>
<p>
    <ul>
        <li>void onStartup(const std::function&lt;void()&gt; &callback);</li>
        <li>void onRequest(const std::function&lt;void()&gt; &callback);</li>
        <li>void onIdle(const std::function&lt;void()&gt; &callback);</li>
        <li>void onShutdown(const std::function&lt;void()&gt; &callback);</li>
    </ul>
</p>
<p>
    The startup callback is called when the Zend engine has loaded your extension 
    and all functions and classes in it were registered. If you want to initialize 
    additional variables in your extension before the functions are going to get called, 
    you can use the onStartup() function and register a callback to run this
    initialization code.
</p>
<p>
    After the Zend engine is initialized, it is ready to process requests. In the
    example above we used the onRequest() method to register a callback that is
    called in front of each request. Next to that, you can also install a callback that gets called
    <i>after</i> each request, when the Zend engine moves to an idle state - waiting
    for the next request. This can be accomplished with the onIdle()
    method in the Php::Extension object.
</p>
<p>
    The fourth callback that you can register is a callback that gets called 
    right before PHP shuts down. If there is anything to clean up, you can install
    such a callback and run the cleanup code from it.
</p>
<h2 id="multi-threading">Watch out for multi-threading</h2>
<p>
    If your extension runs on a multi-threaded PHP installation, you need to take 
    extra care. Most PHP installations (Apache, CLI scripts, etc) serve one
    request at a time, sequentially. There are however PHP installations that use 
    multi-threading and that can serve multiple requests in parallel. If your 
    extension runs on such an environment, you should be aware that your global 
    (and static!) variables can also be accessed by multiple threads at the same 
    time. It is your own responsibility to use technologies like std::mutex or 
    std::atomic to prevent race conditions and conflicts.
</p>
<p>
    If your extension is compiled for a multi-threaded environment, the PHP-CPP
    header files defines the macro ZTS. You can use this macro to check if you
    do have to create special code to deal with threads.
</p>
<p>
<pre class="language-c++"><code>#include &lt;phpcpp.h&gt;

/**
 *  Global variable that store the number of times 
 *  the function updateCounters() has been called in total
 *  @var    int
 */
int invokeTotalCount = 0;

#ifdef ZTS

/**
 *  Mutex so that the 'invokeTotalCount' variable is only accessed
 *  by one process at a time
 *  @var    std::mutex
 */
std::mutex invokeTotalMutex;
 
#endif

/**
 *  Native function that is callable from PHP
 *
 *  This function updates a number of global variables that count
 *  the number of times a function was called
 */
void updateCounters()
{
#ifdef ZTS
    
    // lock the mutex
    std::unique_lock&lt;std::mutex&gt; lock(invokeTotalMutex);

#endif

    // increment counters
    invokeTotalCount++;
}
</code></pre>
</p>
<p>
    Another important thing to realize is that PHP also does this locking 
    internally. If you call a PHP function from you C++ code (like Php::Value("myFunction")()), 
    or when you access a PHP variable in the Php::GLOBALS array (or one of the 
    other super-globals), PHP has to lock something to ensure that no 
    other thread is accessing the same information at the same time. These operations
    can be expensive.
</p>
<p>
    Good rules of thumb for writing native extensions with PHP-CPP therefore
    are:
    <ol type="1">
        <li>Do not use global variables</li>
        <li>Only call other <i>native</i> functions, and don't call back to PHP</li>
    </ol>
</p>
<p>
    In our opinion, these rules should not not be limiting for you. The use of global 
    variables is not considered a very good software design, so you were probably
    not even using them, and the reason why you are writing a native extension is
    because you want to get away from PHP. Calling back to (slow) PHP is the last
    thing you want to do when your application has finally reached native code.
</p>