├── LICENSE ├── README.md └── manuscript ├── AppendixA.md ├── Book.txt ├── Debugging.md ├── Introduction.md ├── NewExtension.md ├── NewExtensionIni.md ├── NewExtensionIni ├── .gitignore ├── config.m4 ├── hello.c └── php_hello.h ├── NewExtensionParam.md ├── NewExtensionParam ├── .gitignore ├── config.m4 ├── hello.c └── php_hello.h ├── NewExtensionTests ├── .gitignore ├── config.m4 ├── hello.c ├── php_hello.h └── tests │ ├── hello.phpt │ ├── hello_with_parameter.phpt │ └── hello_without_parameter.phpt ├── NewExtension_1 ├── .gitignore ├── config.m4 ├── hello.c └── php_hello.h ├── NewExtension_2 ├── .gitignore ├── config.m4 ├── hello.c └── php_hello.h ├── NewObject.md ├── NewObject ├── .gitignore ├── config.m4 ├── hello.c └── php_hello.h ├── PreparePhp.md ├── References.md ├── WritingTests.md └── images └── title_page.png /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | 375 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This is the work repository for the "getting started with PHP extension development" 4 | at [https://leanpub.com/php-extension-development](https://leanpub.com/php-extension-development). 5 | Read ```manuscript/Introduction.md``` for more information. 6 | 7 | # LICENSE 8 | 9 | This work is available under the [Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License](https://creativecommons.org/licenses/by-nc-nd/4.0/). 10 | 11 | Source code examples are available under the [Mozilla Public License Version 2.0](https://github.com/tvlooy/php-ext-dev-book/blob/master/LICENSE). 12 | 13 | -------------------------------------------------------------------------------- /manuscript/AppendixA.md: -------------------------------------------------------------------------------- 1 | # Appendix A 2 | 3 | ## A complete example script to build your PHP 4 | 5 | The variable ```PHP_VERSION``` has to be valid branch name. 6 | The ```PHP_INSTALL_NAME``` can be any name. So, you can have multiple installed 7 | versions of the same PHP version. Eg: with / without debug, more / less extensions 8 | or a different configuration, ... 9 | 10 | ```bash 11 | #!/bin/bash 12 | 13 | # Dependencies for building 14 | # make autoconf gcc bison g++ 15 | 16 | # Dependencies for the selected extensions 17 | # libxml2-dev 18 | # libbz2-dev 19 | # libcurl4-openssl-dev 20 | # libltdl-dev 21 | # libpng12-dev 22 | # libjpeg62-turbo-dev 23 | # libfreetype6-dev 24 | # libxpm-dev 25 | # libimlib2-dev 26 | # libicu-dev 27 | # libreadline6-dev 28 | # libxslt1-dev 29 | # libssl-dev 30 | # libsystemd-dev 31 | 32 | set -e 33 | 34 | # Use a release version like 7.0.8 for a stable release 35 | PHP_VERSION=PHP-7.1 36 | PHP_INSTALL_NAME=7.1-latest 37 | 38 | FILE_OWNER=`whoami` 39 | 40 | TIMEZONE="Europe\/Brussels" 41 | FPM_PORT=9071 42 | FPM_USER=$FILE_OWNER 43 | FPM_GROUP=$FILE_OWNER 44 | APCU_VERSION=5.1.5 45 | 46 | sudo rm -rf /etc/php-${PHP_INSTALL_NAME} 47 | sudo rm -rf /usr/local/php-${PHP_INSTALL_NAME} 48 | 49 | sudo mkdir -p /etc/php-${PHP_INSTALL_NAME}/conf.d 50 | sudo mkdir -p /etc/php-${PHP_INSTALL_NAME}/{cli,fpm}/conf.d 51 | sudo mkdir /usr/local/php-${PHP_INSTALL_NAME} 52 | 53 | sudo chown -R ${FILE_OWNER}:${FILE_OWNER} /etc/php-${PHP_INSTALL_NAME} 54 | sudo chown -R ${FILE_OWNER}:${FILE_OWNER} /usr/local/php-${PHP_INSTALL_NAME} 55 | 56 | # Download 57 | 58 | if [ ! -d php-src ]; then 59 | git clone http://github.com/php/php-src.git 60 | fi 61 | 62 | cd php-src 63 | git checkout ${PHP_VERSION} 64 | 65 | if [ -f Makefile ]; then 66 | make distclean 67 | fi 68 | git clean -xdf 69 | ./buildconf --force 70 | 71 | CONFIGURE_STRING="--prefix=/usr/local/php-${PHP_INSTALL_NAME} \ 72 | --enable-bcmath \ 73 | --with-bz2 \ 74 | --with-zlib \ 75 | --enable-zip \ 76 | --enable-calendar \ 77 | --enable-exif \ 78 | --enable-ftp \ 79 | --with-gettext \ 80 | --with-gd \ 81 | --with-jpeg-dir \ 82 | --with-png-dir \ 83 | --with-freetype-dir \ 84 | --with-xpm-dir \ 85 | --enable-mbstring \ 86 | --enable-mysqlnd \ 87 | --with-mysqli=mysqlnd \ 88 | --with-pdo-mysql=mysqlnd \ 89 | --with-openssl \ 90 | --enable-intl \ 91 | --enable-soap \ 92 | --with-readline \ 93 | --with-curl \ 94 | --with-xsl \ 95 | --disable-cgi" 96 | 97 | # Options for development 98 | CONFIGURE_STRING="$CONFIGURE_STRING \ 99 | --enable-debug" 100 | 101 | # Build FPM 102 | 103 | ./configure \ 104 | $CONFIGURE_STRING \ 105 | --with-config-file-path=/etc/php-${PHP_INSTALL_NAME}/fpm \ 106 | --with-config-file-scan-dir=/etc/php-${PHP_INSTALL_NAME}/fpm/conf.d \ 107 | --disable-cli \ 108 | --enable-fpm \ 109 | --with-fpm-systemd \ 110 | --with-fpm-user=${FPM_USER} \ 111 | --with-fpm-group=${FPM_GROUP} 112 | 113 | make -j`nproc` 114 | make install 115 | 116 | # Install config files 117 | 118 | cp php.ini-production /etc/php-${PHP_INSTALL_NAME}/fpm/php.ini 119 | sed -i "s/;date.timezone =.*/date.timezone = ${TIMEZONE}/" /etc/php-${PHP_INSTALL_NAME}/fpm/php.ini 120 | 121 | cp sapi/fpm/php-fpm.conf.in /etc/php-${PHP_INSTALL_NAME}/fpm/php-fpm.conf 122 | sed -i "s#^include=.*/#include=/etc/php-${PHP_INSTALL_NAME}/fpm/pool.d/#" /etc/php-${PHP_INSTALL_NAME}/fpm/php-fpm.conf 123 | 124 | mkdir /etc/php-${PHP_INSTALL_NAME}/fpm/pool.d/ 125 | cp /usr/local/php-${PHP_INSTALL_NAME}/etc/php-fpm.d/www.conf.default /etc/php-${PHP_INSTALL_NAME}/fpm/pool.d/www.conf 126 | sed -i "s/listen = 127.0.0.1:9000/listen = 127.0.0.1:${FPM_PORT}/g" /etc/php-${PHP_INSTALL_NAME}/fpm/pool.d/www.conf 127 | 128 | sudo tee /etc/systemd/system/php-${PHP_INSTALL_NAME}-fpm.service >/dev/null < /etc/php-${PHP_INSTALL_NAME}/conf.d/opcache.ini 177 | ln -s /etc/php-${PHP_INSTALL_NAME}/conf.d/opcache.ini /etc/php-${PHP_INSTALL_NAME}/cli/conf.d/opcache.ini 178 | ln -s /etc/php-${PHP_INSTALL_NAME}/conf.d/opcache.ini /etc/php-${PHP_INSTALL_NAME}/fpm/conf.d/opcache.ini 179 | 180 | # apcu 181 | printf "\n" | pecl install apcu-${APCU_VERSION} 182 | echo "extension=apcu.so" > /etc/php-${PHP_INSTALL_NAME}/conf.d/apcu.ini 183 | ln -s /etc/php-${PHP_INSTALL_NAME}/conf.d/apcu.ini /etc/php-${PHP_INSTALL_NAME}/cli/conf.d/apcu.ini 184 | ln -s /etc/php-${PHP_INSTALL_NAME}/conf.d/apcu.ini /etc/php-${PHP_INSTALL_NAME}/fpm/conf.d/apcu.ini 185 | 186 | # Symlink PHP into the path 187 | sudo ln -sf /usr/local/php-${PHP_INSTALL_NAME}/bin/php /usr/bin/php-${PHP_INSTALL_NAME} 188 | 189 | pecl install xdebug 190 | 191 | # Ready 192 | 193 | echo "Don't forget to run 'make test'." 194 | ``` 195 | 196 | ## An example script to switch your PHP version 197 | 198 | I have a file named ```pe.sh``` in my ```Tools``` directory. I use this script 199 | to set a PHP engine (therefore pe.sh). 200 | 201 | I call this script from my ```.bashrc``` to set a default PHP version like this: 202 | 203 | ``` 204 | . ~/Tools/pe.sh 7.1 205 | ``` 206 | 207 | I also alias the script in my ```.bash_aliases``` as ```pe``` like this: 208 | 209 | ``` 210 | alias pe='. ~/Tools/pe.sh' 211 | ``` 212 | 213 | This is the ```pe.sh``` script: 214 | 215 | ```bash 216 | #!/bin/bash 217 | 218 | VERSIONS=($(find /usr/local/ -maxdepth 1 -name 'php-*' | sort | cut -c16-)) 219 | VERSION="" 220 | 221 | if [ -n "$1" ]; then 222 | VERSION=$1 223 | fi 224 | 225 | if [[ ! " ${VERSIONS[@]} " =~ " ${VERSION} " ]]; then 226 | PS3="Choose a version: " 227 | select VERSION in ${VERSIONS[*]}; do 228 | echo -e "\nSetting to option $REPLY: $VERSION\n" 229 | /usr/local/php-${VERSION}/bin/php -v 230 | break 231 | done 232 | fi 233 | 234 | PATH=/usr/local/php-${VERSION}/bin:/usr/local/php-${VERSION}/sbin 235 | PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games 236 | PATH=$PATH:$HOME/Tools 237 | 238 | export PATH="$PATH" 239 | ``` 240 | -------------------------------------------------------------------------------- /manuscript/Book.txt: -------------------------------------------------------------------------------- 1 | Introduction.md 2 | PreparePhp.md 3 | NewExtension.md 4 | Debugging.md 5 | NewExtensionParam.md 6 | WritingTests.md 7 | NewExtensionIni.md 8 | NewObject.md 9 | AppendixA.md 10 | References.md 11 | -------------------------------------------------------------------------------- /manuscript/Debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 | Things will not always work and C is more difficult than PHP. So how do you 4 | debug a problem? For example: 5 | 6 | ## Segfaults 7 | 8 | I changed the function to print "hello" with an upper case "H", but made a 9 | programming mistake. 10 | 11 | ```c 12 | /* for toupper() */ 13 | #include 14 | 15 | PHP_FUNCTION(hello) { 16 | char *name = NULL; 17 | char *hello = "hello"; 18 | size_t name_len = 0; 19 | 20 | hello[0] = toupper(hello[0]); 21 | 22 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) { 23 | return; 24 | } 25 | 26 | php_printf("%s %s\n", hello, name); 27 | RETURN_TRUE; 28 | } 29 | ``` 30 | 31 | Let's see what happens when we run it: 32 | 33 | ``` 34 | $ php -dextension=modules/hello.so -a 35 | Interactive shell 36 | 37 | php > hello('world'); 38 | Segmentation fault 39 | ``` 40 | 41 | Now what? 42 | 43 | ### Get a coredump 44 | 45 | Enable coredumps and run the program again: 46 | 47 | ``` 48 | ulimit -c unlimited 49 | ``` 50 | 51 | ``` 52 | $ php -dextension=modules/hello.so -r "hello('world');" 53 | Segmentation fault (core dumped) 54 | ``` 55 | 56 | A file named ```core``` will be created. 57 | 58 | ### Locating the problem 59 | 60 | Run the GNU debugger on php with your coredump. 61 | 62 | ``` 63 | $ gdb php core 64 | ... 65 | Core was generated by `php -dextension=modules/hello.so -r hello('world');'. 66 | Program terminated with signal SIGSEGV, Segmentation fault. 67 | #0 0x00007fe35992896e in zif_hello (execute_data=0x7fe35a6130a0, return_value=0x7fe35a613090) at .../manuscript/CoreDump/hello.c:42 68 | 42 hello[0] = toupper(hello[0]); 69 | ``` 70 | 71 | It immediately points out that there is a problem with the ```toupper()``` in ```hello.c``` at line 42. 72 | It will not always be this obvious, sometimes you want to get a backtrace to see how you got at this line. Run ```bt``` to get a full stack trace: 73 | 74 | ``` 75 | (gdb) bt 76 | #0 0x00007fe35992896e in zif_hello (execute_data=0x7fe35a6130a0, return_value=0x7fe35a613090) at .../manuscript/CoreDump/hello.c:42 77 | #1 0x0000000000b052d8 in ZEND_DO_ICALL_SPEC_HANDLER () at .../Zend/zend_vm_execute.h:586 78 | #2 0x0000000000b04d04 in execute_ex (ex=0x7fe35a613030) at .../Zend/zend_vm_execute.h:414 79 | #3 0x0000000000b04e15 in zend_execute (op_array=0x7fe35a682000, return_value=0x7ffc179a0af0) at .../Zend/zend_vm_execute.h:458 80 | #4 0x0000000000a8ea37 in zend_eval_stringl (str=0x1b1a680 "hello('world');", str_len=15, retval_ptr=0x0, string_name=0x1138ae4 "Command line code") 81 | at .../Zend/zend_execute_API.c:1135 82 | #5 0x0000000000a8ec5c in zend_eval_stringl_ex (str=0x1b1a680 "hello('world');", str_len=15, retval_ptr=0x0, string_name=0x1138ae4 "Command line code", 83 | handle_exceptions=1) at .../Zend/zend_execute_API.c:1176 84 | #6 0x0000000000a8ece2 in zend_eval_string_ex (str=0x1b1a680 "hello('world');", retval_ptr=0x0, string_name=0x1138ae4 "Command line code", handle_exceptions=1) 85 | at .../Zend/zend_execute_API.c:1187 86 | #7 0x0000000000b6df9d in do_cli (argc=4, argv=0x1b3dcb0) at .../sapi/cli/php_cli.c:1005 87 | #8 0x0000000000b6f065 in main (argc=4, argv=0x1b3dcb0) at .../sapi/cli/php_cli.c:1344 88 | ``` 89 | 90 | You can also step debug it. Run the program again under gdb and set a breakpoint on line 38. 91 | 92 | ``` 93 | $ gdb --args php -dextension=modules/hello.so -r "hello('world');" 94 | ... 95 | (gdb) break hello.c:38 96 | No source file named hello.c. 97 | Make breakpoint pending on future shared library load? (y or [n]) y 98 | Breakpoint 1 (hello.c:38) pending. 99 | ``` 100 | 101 | Then actually run the program and step through it. 102 | 103 | ``` 104 | (gdb) run 105 | Starting program: php -dextension=modules/hello.so -r hello\(\'world\'\)\; 106 | 107 | Breakpoint 1, zif_hello (execute_data=0x7fffecc130a0, return_value=0x7fffecc13090) at .../manuscript/CoreDump/hello.c:38 108 | 38 char *name = NULL; 109 | (gdb) s 110 | 39 char *hello = "hello"; 111 | (gdb) s 112 | 40 size_t name_len = 0; 113 | (gdb) s 114 | 42 hello[0] = toupper(hello[0]); 115 | (gdb) print hello 116 | $1 = 0x7fffebf28a09 "hello" 117 | (gdb) s 118 | toupper (c=104) at ctype.c:52 119 | 52 return c >= -128 && c < 256 ? __ctype_toupper[c] : c; 120 | (gdb) s 121 | 53 } 122 | (gdb) s 123 | 124 | Program received signal SIGSEGV, Segmentation fault. 125 | 0x00007fffebf2896e in zif_hello (execute_data=0x7fffecc130a0, return_value=0x7fffecc13090) at .../manuscript/CoreDump/hello.c:42 126 | 42 hello[0] = toupper(hello[0]); 127 | ``` 128 | 129 | The problem with the program is that the ```char *hello = "hello";``` should have been a ```char hello[] = "hello";```. 130 | Rookie mistake, read "The C programming language" (K&R) section 5.5 "Character Pointers and Functions" for more information. 131 | 132 | ## Memory leaks 133 | 134 | Let's make the function leak some memory by allocating 128 bytes of memory and not releasing it: 135 | 136 | ```c 137 | PHP_FUNCTION(hello) { 138 | char *name = NULL; 139 | size_t name_len = 0; 140 | 141 | emalloc(128); 142 | 143 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) { 144 | return; 145 | } 146 | 147 | php_printf("Hello %s\n", name); 148 | RETURN_TRUE; 149 | } 150 | ``` 151 | 152 | Let's see what happens when we run it: 153 | 154 | ``` 155 | $ php -dextension=modules/hello.so -r "hello('world');" 156 | Hello world 157 | [Sun Jul 24 17:30:07 2016] Script: '-' 158 | .../manuscript/CoreDump/hello.c(38) : Freeing 0x7F25B10731E0 (128 bytes), script=- 159 | === Total 1 memory leaks detected === 160 | ``` 161 | 162 | If PHP is built with ```--enable-debug``` and the ini flag ```report_memleaks``` is ```On``` (this is by default), memory leaks will be reported. 163 | You can turn it off and PHP will no longer print info about the leak: 164 | 165 | ``` 166 | $ php -dreport_memleaks=Off -dextension=modules/hello.so -r "hello('world');" 167 | Hello world 168 | ``` 169 | 170 | The reason why the engine detects the leak is because ```emalloc()``` is part of the PHP API and it tracks allocations. 171 | Never use the system default allocators like ```malloc()``` to work with memory, the engine will not detect leaks. 172 | More information about PHP memory API at http://php.net/manual/en/internals2.memory.management.php 173 | 174 | Tools like Valgrind can help you debug memory leaks. 175 | 176 | ### Valgrind 177 | 178 | Note: I compiled Valgrind from source because the latest version has a bug that makes it unusable. 179 | See http://valgrind.org/downloads/repository.html for instructions. 180 | 181 | Make sure to turn off Zend Memory Manager (ZMM) when debugging leaks with Valgrind. 182 | It will force the system to use the default system allocators. 183 | 184 | Also make sure Zend doesn't unload the modules. Otherwise Valgrind will have gaps in it's output. 185 | It will try to make a report but the modules are already gone. 186 | 187 | ``` 188 | $ ZEND_DONT_UNLOAD_MODULES=1 USE_ZEND_ALLOC=0 valgrind --leak-check=yes php -dextension=modules/hello.so -r "hello('world');" 189 | ==30599== Memcheck, a memory error detector 190 | ==30599== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. 191 | ==30599== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info 192 | ==30599== Command: php -dextension=modules/hello.so -r hello('world'); 193 | ==30599== 194 | Hello world 195 | ==30599== 196 | ==30599== HEAP SUMMARY: 197 | ==30599== in use at exit: 80,645 bytes in 42 blocks 198 | ==30599== total heap usage: 31,207 allocs, 31,165 frees, 3,639,418 bytes allocated 199 | ==30599== 200 | ==30599== 128 bytes in 1 blocks are definitely lost in loss record 30 of 38 201 | ==30599== at 0x4C2DBB6: malloc (vg_replace_malloc.c:299) 202 | ==30599== by 0xA70761: _emalloc (zend_alloc.c:2446) 203 | ==30599== by 0x10C4397C: zif_hello (hello.c:38) 204 | ==30599== by 0xB052D7: ZEND_DO_ICALL_SPEC_HANDLER (zend_vm_execute.h:586) 205 | ==30599== by 0xB04D03: execute_ex (zend_vm_execute.h:414) 206 | ==30599== by 0xB04E14: zend_execute (zend_vm_execute.h:458) 207 | ==30599== by 0xA8EA36: zend_eval_stringl (zend_execute_API.c:1135) 208 | ==30599== by 0xA8EC5B: zend_eval_stringl_ex (zend_execute_API.c:1176) 209 | ==30599== by 0xA8ECE1: zend_eval_string_ex (zend_execute_API.c:1187) 210 | ==30599== by 0xB6DF9C: do_cli (php_cli.c:1005) 211 | ==30599== by 0xB6F064: main (php_cli.c:1344) 212 | ==30599== 213 | ==30599== LEAK SUMMARY: 214 | ==30599== definitely lost: 128 bytes in 1 blocks 215 | ==30599== indirectly lost: 0 bytes in 0 blocks 216 | ==30599== possibly lost: 0 bytes in 0 blocks 217 | ==30599== still reachable: 80,517 bytes in 41 blocks 218 | ==30599== suppressed: 0 bytes in 0 blocks 219 | ==30599== Reachable blocks (those to which a pointer was found) are not shown. 220 | ==30599== To see them, rerun with: --leak-check=full --show-leak-kinds=all 221 | ==30599== 222 | ==30599== For counts of detected and suppressed errors, rerun with: -v 223 | ==30599== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) 224 | ``` 225 | 226 | The output is clear "definitely lost: 128 bytes in 1 blocks", and it points to "zend_alloc" 227 | functionality in the function "zif_hello" in file "hello.c" line 38. 228 | -------------------------------------------------------------------------------- /manuscript/Introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This book is open-sourced at [https://github.com/tvlooy/php-ext-dev-book](https://github.com/tvlooy/php-ext-dev-book) 4 | and available under the creative commons "Attribution-NonCommercial-NoDerivs CC BY-NC-ND" license. 5 | 6 | Visit [https://creativecommons.org/licenses/by-nc-nd/4.0/](https://leanpub.com/php-extension-development) for more info. 7 | 8 | Source code examples are available under the "Mozilla Public License Version 2.0". 9 | 10 | Visit [https://www.mozilla.org/en-US/MPL/2.0/](https://www.mozilla.org/en-US/MPL/2.0/) for more info. 11 | 12 | The primary objective of this book is helping other developers. I wanted to learn 13 | about extension writing and PHP core and not a lot of information is available. 14 | I decided to document some of it for you, the reader, while I am learning myself ... 15 | 16 | The book covers PHP 7. 17 | 18 | If this information was useful for you, I would be happy with kudos on Twitter or 19 | something. Do share this with others. If you prefer to donate money, the money 20 | will be spent on donations to other FOSS projects. There is no interest in making 21 | financial benefits from this book. 22 | 23 | I welcome fixes, patches, comments, ... 24 | -------------------------------------------------------------------------------- /manuscript/NewExtension.md: -------------------------------------------------------------------------------- 1 | # Your first simple extension 2 | 3 | Extensions should follow the PHP coding standards. You can find these 4 | at [php-src CODING_STANDARDS](https://github.com/php/php-src/blob/master/CODING_STANDARDS). 5 | 6 | ## Hello world 7 | 8 | Make a directory ```hello``` and put these files in it: 9 | 10 | ```m4 11 | dnl config.m4 12 | 13 | PHP_ARG_ENABLE(hello, whether to enable hello extension, 14 | [ --enable-hello Enable hello extension]) 15 | 16 | if test "$PHP_HELLO" != "no"; then 17 | PHP_NEW_EXTENSION(hello, hello.c, $ext_shared) 18 | fi 19 | ``` 20 | 21 | The ```config.m4``` file enables an argument ```--enable-hello``` to build the 22 | extension with PHP. You have to align the flag or it will not be in line when 23 | you ```./configure --help```. In these examples I will always build extensions 24 | separately. 25 | 26 | ```c 27 | /* php_hello.h */ 28 | 29 | #define PHP_HELLO_EXTNAME "hello" 30 | #define PHP_HELLO_VERSION "0.1" 31 | 32 | extern zend_module_entry hello_module_entry; 33 | #define phpext_hello_ptr &check_hello_entry 34 | 35 | PHP_FUNCTION(hello); 36 | ``` 37 | 38 | The ```extern``` and ```define``` are needed to make the module available if you 39 | would like to compile it into PHP with ```--enable-hello``` instead of as a module. 40 | 41 | ```c 42 | /* hello.c */ 43 | 44 | /* include PHP API */ 45 | #include "php.h" 46 | 47 | /* this module's header file */ 48 | #include "php_hello.h" 49 | 50 | /* provide info for the reflection API */ 51 | ZEND_BEGIN_ARG_INFO(arginfo_hello, 0) 52 | ZEND_END_ARG_INFO(); 53 | 54 | /* define the function we want to add */ 55 | zend_function_entry hello_functions[] = { 56 | PHP_FE(hello, arginfo_hello) 57 | PHP_FE_END 58 | }; 59 | 60 | /* "hello_functions" refers to the struct defined above */ 61 | zend_module_entry hello_module_entry = { 62 | STANDARD_MODULE_HEADER, 63 | PHP_HELLO_EXTNAME, 64 | hello_functions, /* Function entries */ 65 | NULL, /* Module init */ 66 | NULL, /* Module shutdown */ 67 | NULL, /* Request init */ 68 | NULL, /* Request shutdown */ 69 | NULL, /* Module information */ 70 | PHP_HELLO_VERSION, 71 | STANDARD_MODULE_PROPERTIES 72 | }; 73 | 74 | /* install module */ 75 | ZEND_GET_MODULE(hello) 76 | 77 | PHP_FUNCTION(hello) { 78 | /* php_printf is PHP's version of printf */ 79 | php_printf("Hello world!\n"); 80 | } 81 | ``` 82 | 83 | Build the extension with: 84 | 85 | ```bash 86 | phpize 87 | ./configure 88 | make 89 | ``` 90 | 91 | You can test your extension by installing it and loading if with an ini directive: 92 | 93 | ```bash 94 | make install 95 | echo "extension=hello.so" > /etc/php7/conf.d/hello.ini 96 | ln -s /etc/php7/conf.d/hello.ini /etc/php7/cli/conf.d/hello.ini 97 | ln -s /etc/php7/conf.d/hello.ini /etc/php7/fpm/conf.d/hello.ini 98 | ``` 99 | 100 | Or, you can not install it yet and just load it from the build directory: 101 | 102 | ```bash 103 | php -a -dextension=modules/hello.so 104 | php > hello(); 105 | Hello world! 106 | ``` 107 | 108 | Or you can test your extension with ```-r```, I'll show a variation: 109 | 110 | ```bash 111 | php -dextension_dir=modules \ 112 | -dextension=hello.so \ 113 | -n \ 114 | -r 'hello();' 115 | Hello world! 116 | ``` 117 | 118 | The optional ```-n``` flag makes sure you don't load the default ini file. 119 | 120 | I you are done with your development and want to commit your code to Git, you will 121 | notice that the directory is full of build files you don't need. Clean up these with: 122 | 123 | ``` 124 | phpize --clean 125 | ``` 126 | 127 | ## The zend_module_entry 128 | 129 | The zend_module_entry is where the life cycle of the module is configured. In 130 | this first example, we set NULL for all all events. 131 | 132 | When your SAPI starts and the module is loaded, the configured "module init" is 133 | called. When the SAPI stops, the configured "module shutdown" is called. 134 | 135 | Once a SAPI has your module loaded, it can serve multiple requests. On every 136 | request, the configured "request init" and "request shutdown" is called. 137 | 138 | For example for Apache2 with mod_php that is configured 139 | with ```MaxRequestsPerChild 3```, a child process will look like this: 140 | 141 | +--------------+ 142 | | MINIT | 143 | +--------------+ 144 | | RINIT | 145 | | PHP script | 146 | | RSHUTDOWN | 147 | +--------------+ 148 | | RINIT | 149 | | PHP script | 150 | | RSHUTDOWN | 151 | +--------------+ 152 | | RINIT | 153 | | PHP script | 154 | | RSHUTDOWN | 155 | +--------------+ 156 | | MSHUTDOWN | 157 | +--------------+ 158 | 159 | ## More functions 160 | 161 | Adding more functions is a matter of adding them to the hello_functions, writing 162 | the actual function and exporting it in the header. 163 | 164 | ```c 165 | /* hello.c */ 166 | 167 | /* provide info for the reflection API */ 168 | ZEND_BEGIN_ARG_INFO(arginfo_hi_world, 0) 169 | ZEND_END_ARG_INFO(); 170 | ZEND_BEGIN_ARG_INFO(arginfo_bye_world, 0) 171 | ZEND_END_ARG_INFO(); 172 | 173 | zend_function_entry hello_functions[] = { 174 | PHP_FE(hi_world, arginfo_hi_world) 175 | PHP_FE(bye_world, arginfo_bye_world) 176 | PHP_FE_END 177 | }; 178 | 179 | PHP_FUNCTION(hi_world) { 180 | php_printf("Hello world!\n"); 181 | } 182 | 183 | PHP_FUNCTION(bye_world) { 184 | php_printf("Goodbye world!\n"); 185 | } 186 | ``` 187 | 188 | ```c 189 | /* php_hello.h */ 190 | 191 | PHP_FUNCTION(hi_world); 192 | PHP_FUNCTION(bye_world); 193 | ``` 194 | 195 | Congrats! You just built your first PHP extension. 196 | -------------------------------------------------------------------------------- /manuscript/NewExtensionIni.md: -------------------------------------------------------------------------------- 1 | # INI settings 2 | 3 | Let's say we want to configure with an INI setting ```hello.yell``` that defines 4 | whether or not our hello function has to yell. 5 | 6 | In the header, add two prototypes to the header: 7 | 8 | ```c 9 | PHP_MINIT_FUNCTION(hello); 10 | PHP_MSHUTDOWN_FUNCTION(hello); 11 | ``` 12 | 13 | Use a init and shutdown function in the module_entry. These will be called 14 | during the startup (MINIT) of the SAPI and during the shutdown (MSHUTDOWN). 15 | 16 | ```c 17 | zend_module_entry hello_module_entry = { 18 | STANDARD_MODULE_HEADER, 19 | PHP_HELLO_EXTNAME, 20 | hello_functions, /* Function entries */ 21 | PHP_MINIT(hello), /* Module init */ 22 | PHP_MSHUTDOWN(hello), /* Module shutdown */ 23 | NULL, /* Request init */ 24 | NULL, /* Request shutdown */ 25 | NULL, /* Module information */ 26 | PHP_HELLO_VERSION, 27 | STANDARD_MODULE_PROPERTIES 28 | }; 29 | ``` 30 | 31 | These two functions look like this: 32 | 33 | ```c 34 | PHP_MINIT_FUNCTION(hello) { 35 | REGISTER_INI_ENTRIES(); 36 | return SUCCESS; 37 | } 38 | 39 | PHP_MSHUTDOWN_FUNCTION(hello) { 40 | UNREGISTER_INI_ENTRIES(); 41 | return SUCCESS; 42 | } 43 | ``` 44 | 45 | Now we are ready to actually register the INI setting: 46 | 47 | ```c 48 | PHP_INI_BEGIN() 49 | PHP_INI_ENTRY("hello.yell", "0", PHP_INI_ALL, NULL) 50 | PHP_INI_END() 51 | ``` 52 | 53 | And use the INI setting in our function: 54 | 55 | ```c 56 | /* function hello(string $name): bool */ 57 | PHP_FUNCTION(hello) { 58 | int i = 0; 59 | zend_string *name; 60 | 61 | ZEND_PARSE_PARAMETERS_START(1, 1) 62 | Z_PARAM_STR(name) 63 | ZEND_PARSE_PARAMETERS_END(); 64 | 65 | if (INI_BOOL("hello.yell") == 1) { 66 | while (name->val[i]) { 67 | name->val[i] = toupper(name->val[i]); 68 | i++; 69 | } 70 | php_printf("HELLO %s!\n", ZSTR_VAL(name)); 71 | } else { 72 | php_printf("Hello %s\n", ZSTR_VAL(name)); 73 | } 74 | 75 | RETURN_TRUE; 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /manuscript/NewExtensionIni/.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | *.lo 3 | *.la 4 | .libs 5 | acinclude.m4 6 | aclocal.m4 7 | autom4te.cache 8 | build 9 | config.guess 10 | config.h 11 | config.h.in 12 | config.log 13 | config.nice 14 | config.status 15 | config.sub 16 | configure 17 | configure.in 18 | include 19 | install-sh 20 | libtool 21 | ltmain.sh 22 | Makefile 23 | Makefile.fragments 24 | Makefile.global 25 | Makefile.objects 26 | missing 27 | mkinstalldirs 28 | modules 29 | run-tests.php 30 | tests/*/*.diff 31 | tests/*/*.out 32 | tests/*/*.php 33 | tests/*/*.exp 34 | tests/*/*.log 35 | tests/*/*.sh 36 | -------------------------------------------------------------------------------- /manuscript/NewExtensionIni/config.m4: -------------------------------------------------------------------------------- 1 | dnl config.m4 2 | 3 | PHP_ARG_ENABLE(hello, whether to enable hello extension, 4 | [ --enable-hello Enable hello extension]) 5 | 6 | if test "$PHP_HELLO" != "no"; then 7 | PHP_NEW_EXTENSION(hello, hello.c, $ext_shared) 8 | fi 9 | 10 | -------------------------------------------------------------------------------- /manuscript/NewExtensionIni/hello.c: -------------------------------------------------------------------------------- 1 | /* hello.c */ 2 | 3 | /* include PHP API */ 4 | #include "php.h" 5 | #include "php_ini.h" 6 | 7 | /* this module's header file */ 8 | #include "php_hello.h" 9 | 10 | /* provide info for the reflection API */ 11 | ZEND_BEGIN_ARG_INFO(arginfo_hello, 0) 12 | ZEND_ARG_INFO(0, name) 13 | ZEND_END_ARG_INFO(); 14 | 15 | /* define the function we want to add */ 16 | zend_function_entry hello_functions[] = { 17 | PHP_FE(hello, arginfo_hello) 18 | PHP_FE_END 19 | }; 20 | 21 | /* "hello_functions" refers to the struct defined above */ 22 | zend_module_entry hello_module_entry = { 23 | STANDARD_MODULE_HEADER, 24 | PHP_HELLO_EXTNAME, 25 | hello_functions, /* Function entries */ 26 | PHP_MINIT(hello), /* Module init */ 27 | PHP_MSHUTDOWN(hello), /* Module shutdown */ 28 | NULL, /* Request init */ 29 | NULL, /* Request shutdown */ 30 | NULL, /* Module information */ 31 | PHP_HELLO_VERSION, 32 | STANDARD_MODULE_PROPERTIES 33 | }; 34 | 35 | PHP_INI_BEGIN() 36 | PHP_INI_ENTRY("hello.yell", "0", PHP_INI_ALL, NULL) 37 | PHP_INI_END() 38 | 39 | PHP_MINIT_FUNCTION(hello) { 40 | REGISTER_INI_ENTRIES(); 41 | return SUCCESS; 42 | } 43 | 44 | PHP_MSHUTDOWN_FUNCTION(hello) { 45 | UNREGISTER_INI_ENTRIES(); 46 | return SUCCESS; 47 | } 48 | 49 | /* install module */ 50 | ZEND_GET_MODULE(hello) 51 | 52 | /* function hello(string $name): bool */ 53 | PHP_FUNCTION(hello) { 54 | int i = 0; 55 | zend_string *name; 56 | 57 | ZEND_PARSE_PARAMETERS_START(1, 1) 58 | Z_PARAM_STR(name) 59 | ZEND_PARSE_PARAMETERS_END(); 60 | 61 | if (INI_BOOL("hello.yell") == 1) { 62 | while (name->val[i]) { 63 | name->val[i] = toupper(name->val[i]); 64 | i++; 65 | } 66 | php_printf("HELLO %s!\n", ZSTR_VAL(name)); 67 | } else { 68 | php_printf("Hello %s\n", ZSTR_VAL(name)); 69 | } 70 | 71 | RETURN_TRUE; 72 | } 73 | -------------------------------------------------------------------------------- /manuscript/NewExtensionIni/php_hello.h: -------------------------------------------------------------------------------- 1 | /* php_hello.h */ 2 | 3 | #define PHP_HELLO_EXTNAME "hello" 4 | #define PHP_HELLO_VERSION "0.1" 5 | 6 | extern zend_module_entry hello_module_entry; 7 | #define phpext_hello_ptr &check_hello_entry 8 | 9 | PHP_MINIT_FUNCTION(hello); 10 | PHP_MSHUTDOWN_FUNCTION(hello); 11 | 12 | PHP_FUNCTION(hello); 13 | -------------------------------------------------------------------------------- /manuscript/NewExtensionParam.md: -------------------------------------------------------------------------------- 1 | # Function input and output 2 | 3 | Normal functions have input and output, let's add these. 4 | 5 | ## Return value 6 | 7 | First we make our own function return success: 8 | 9 | ```c 10 | /* php_hello.h */ 11 | 12 | PHP_FUNCTION(hello) { 13 | php_printf("Hello world!\n"); 14 | RETURN_BOOL(true); 15 | } 16 | ``` 17 | 18 | You could also return "Hello world" instead of print it. There are functions for 19 | various data types: 20 | 21 | ```c 22 | RETURN_STRING("Hello world!\n"); 23 | RETURN_LONG(42); 24 | RETURN_DOUBLE(3.1415926535); 25 | RETURN_BOOL(true); 26 | RETURN_NULL(); 27 | ``` 28 | 29 | Now clean the build and re-compile the module: 30 | 31 | ```bash 32 | make clean all 33 | make 34 | ``` 35 | 36 | Note that instead of RETURN_BOOL(), you can also use the RETURN_TRUE and 37 | RETURN_FALSE macros (without parentheses). Besides that there are also these 38 | return types available: ```RESOURCE```, ```ARRAY``` (or ```HASH```) and ```OBJECT```. 39 | 40 | ## Input argument 41 | 42 | Now that the function returns something. Let's accept an input argument. Change 43 | the function entry to this: 44 | 45 | ```c 46 | ZEND_BEGIN_ARG_INFO(arginfo_hello, 0) 47 | ZEND_ARG_INFO(0, name) 48 | ZEND_END_ARG_INFO(); 49 | 50 | zend_function_entry hello_functions[] = { 51 | PHP_FE(hello, arginfo_hello) 52 | PHP_FE_END 53 | }; 54 | ``` 55 | 56 | In the function, parse the parameter. I will give you 2 examples. The first is 57 | the old way, that is still works and is in use in a lot of places. The second 58 | is the new way. 59 | 60 | Old way: 61 | 62 | ```c 63 | /* function hello(string $name): bool */ 64 | PHP_FUNCTION(hello) { 65 | char *name = NULL; 66 | size_t name_len = 0; 67 | 68 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) { 69 | return; 70 | } 71 | 72 | php_printf("Hello %s\n", name); 73 | 74 | RETURN_TRUE; 75 | } 76 | ``` 77 | 78 | New way: 79 | 80 | ```c 81 | /* function hello(string $name): bool */ 82 | PHP_FUNCTION(hello) { 83 | zend_string *name; 84 | 85 | ZEND_PARSE_PARAMETERS_START(1, 1) 86 | Z_PARAM_STR(name) 87 | ZEND_PARSE_PARAMETERS_END(); 88 | 89 | php_printf("Hello %s\n", ZSTR_VAL(name)); 90 | 91 | RETURN_TRUE; 92 | } 93 | ``` 94 | 95 | The ```macro ZEND_PARSE_PARAMETERS_START``` has 2 parameters. The minumum 96 | and maximum number of arguments the function accepts. 97 | 98 | Note that the ```arginfo_hello``` is used by the reflection API. You could omit 99 | it but that would mean reflection would not work on your function. You can dump 100 | reflection information of your own module with: 101 | 102 | ```bash 103 | php -dextension=modules/hello.so --re hello 104 | ``` 105 | 106 | This would output something like: 107 | 108 | ``` 109 | Extension [ extension #48 hello version 0.1 ] { 110 | 111 | - Functions { 112 | Function [ function hello ] { 113 | 114 | - Parameters [1] { 115 | Parameter #0 [ $name ] 116 | } 117 | } 118 | } 119 | } 120 | ``` 121 | -------------------------------------------------------------------------------- /manuscript/NewExtensionParam/.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | *.lo 3 | *.la 4 | .libs 5 | acinclude.m4 6 | aclocal.m4 7 | autom4te.cache 8 | build 9 | config.guess 10 | config.h 11 | config.h.in 12 | config.log 13 | config.nice 14 | config.status 15 | config.sub 16 | configure 17 | configure.in 18 | include 19 | install-sh 20 | libtool 21 | ltmain.sh 22 | Makefile 23 | Makefile.fragments 24 | Makefile.global 25 | Makefile.objects 26 | missing 27 | mkinstalldirs 28 | modules 29 | run-tests.php 30 | tests/*/*.diff 31 | tests/*/*.out 32 | tests/*/*.php 33 | tests/*/*.exp 34 | tests/*/*.log 35 | tests/*/*.sh 36 | -------------------------------------------------------------------------------- /manuscript/NewExtensionParam/config.m4: -------------------------------------------------------------------------------- 1 | dnl config.m4 2 | 3 | PHP_ARG_ENABLE(hello, whether to enable hello extension, 4 | [ --enable-hello Enable hello extension]) 5 | 6 | if test "$PHP_HELLO" != "no"; then 7 | PHP_NEW_EXTENSION(hello, hello.c, $ext_shared) 8 | fi 9 | 10 | -------------------------------------------------------------------------------- /manuscript/NewExtensionParam/hello.c: -------------------------------------------------------------------------------- 1 | /* hello.c */ 2 | 3 | /* include PHP API */ 4 | #include "php.h" 5 | 6 | /* this module's header file */ 7 | #include "php_hello.h" 8 | 9 | /* provide info for the reflection API */ 10 | ZEND_BEGIN_ARG_INFO(arginfo_hello, 0) 11 | ZEND_ARG_INFO(0, name) 12 | ZEND_END_ARG_INFO(); 13 | 14 | /* define the function we want to add */ 15 | zend_function_entry hello_functions[] = { 16 | PHP_FE(hello, arginfo_hello) 17 | PHP_FE_END 18 | }; 19 | 20 | /* "hello_functions" refers to the struct defined above */ 21 | zend_module_entry hello_module_entry = { 22 | STANDARD_MODULE_HEADER, 23 | PHP_HELLO_EXTNAME, 24 | hello_functions, /* Function entries */ 25 | NULL, /* Module init */ 26 | NULL, /* Module shutdown */ 27 | NULL, /* Request init */ 28 | NULL, /* Request shutdown */ 29 | NULL, /* Module information */ 30 | PHP_HELLO_VERSION, 31 | STANDARD_MODULE_PROPERTIES 32 | }; 33 | 34 | /* install module */ 35 | ZEND_GET_MODULE(hello) 36 | 37 | /* function hello(string $name): bool */ 38 | PHP_FUNCTION(hello) { 39 | zend_string *name; 40 | 41 | ZEND_PARSE_PARAMETERS_START(1, 1) 42 | Z_PARAM_STR(name) 43 | ZEND_PARSE_PARAMETERS_END(); 44 | 45 | php_printf("Hello %s\n", ZSTR_VAL(name)); 46 | 47 | RETURN_TRUE; 48 | } 49 | -------------------------------------------------------------------------------- /manuscript/NewExtensionParam/php_hello.h: -------------------------------------------------------------------------------- 1 | /* php_hello.h */ 2 | 3 | #define PHP_HELLO_EXTNAME "hello" 4 | #define PHP_HELLO_VERSION "0.1" 5 | 6 | extern zend_module_entry hello_module_entry; 7 | #define phpext_hello_ptr &check_hello_entry 8 | 9 | PHP_FUNCTION(hello); 10 | -------------------------------------------------------------------------------- /manuscript/NewExtensionTests/.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | *.lo 3 | *.la 4 | .libs 5 | acinclude.m4 6 | aclocal.m4 7 | autom4te.cache 8 | build 9 | config.guess 10 | config.h 11 | config.h.in 12 | config.log 13 | config.nice 14 | config.status 15 | config.sub 16 | configure 17 | configure.in 18 | include 19 | install-sh 20 | libtool 21 | ltmain.sh 22 | Makefile 23 | Makefile.fragments 24 | Makefile.global 25 | Makefile.objects 26 | missing 27 | mkinstalldirs 28 | modules 29 | run-tests.php 30 | tests/*/*.diff 31 | tests/*/*.out 32 | tests/*/*.php 33 | tests/*/*.exp 34 | tests/*/*.log 35 | tests/*/*.sh 36 | -------------------------------------------------------------------------------- /manuscript/NewExtensionTests/config.m4: -------------------------------------------------------------------------------- 1 | dnl config.m4 2 | 3 | PHP_ARG_ENABLE(hello, whether to enable hello extension, 4 | [ --enable-hello Enable hello extension]) 5 | 6 | if test "$PHP_HELLO" != "no"; then 7 | PHP_NEW_EXTENSION(hello, hello.c, $ext_shared) 8 | fi 9 | 10 | -------------------------------------------------------------------------------- /manuscript/NewExtensionTests/hello.c: -------------------------------------------------------------------------------- 1 | /* hello.c */ 2 | 3 | /* include PHP API */ 4 | #include "php.h" 5 | 6 | /* this module's header file */ 7 | #include "php_hello.h" 8 | 9 | /* provide info for the reflection API */ 10 | ZEND_BEGIN_ARG_INFO(arginfo_hello, 0) 11 | ZEND_ARG_INFO(0, name) 12 | ZEND_END_ARG_INFO(); 13 | 14 | /* define the function we want to add */ 15 | zend_function_entry hello_functions[] = { 16 | PHP_FE(hello, arginfo_hello) 17 | PHP_FE_END 18 | }; 19 | 20 | /* "hello_functions" refers to the struct defined above */ 21 | zend_module_entry hello_module_entry = { 22 | STANDARD_MODULE_HEADER, 23 | PHP_HELLO_EXTNAME, 24 | hello_functions, /* Function entries */ 25 | NULL, /* Module init */ 26 | NULL, /* Module shutdown */ 27 | NULL, /* Request init */ 28 | NULL, /* Request shutdown */ 29 | NULL, /* Module information */ 30 | PHP_HELLO_VERSION, 31 | STANDARD_MODULE_PROPERTIES 32 | }; 33 | 34 | /* install module */ 35 | ZEND_GET_MODULE(hello) 36 | 37 | /* function hello(string $name): bool */ 38 | PHP_FUNCTION(hello) { 39 | zend_string *name; 40 | 41 | ZEND_PARSE_PARAMETERS_START(1, 1) 42 | Z_PARAM_STR(name) 43 | ZEND_PARSE_PARAMETERS_END(); 44 | 45 | php_printf("Hello %s\n", ZSTR_VAL(name)); 46 | 47 | RETURN_TRUE; 48 | } 49 | -------------------------------------------------------------------------------- /manuscript/NewExtensionTests/php_hello.h: -------------------------------------------------------------------------------- 1 | /* php_hello.h */ 2 | 3 | #define PHP_HELLO_EXTNAME "hello" 4 | #define PHP_HELLO_VERSION "0.1" 5 | 6 | extern zend_module_entry hello_module_entry; 7 | #define phpext_hello_ptr &check_hello_entry 8 | 9 | PHP_FUNCTION(hello); 10 | -------------------------------------------------------------------------------- /manuscript/NewExtensionTests/tests/hello.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | hello() function - basic test for hello() 3 | --FILE-- 4 | 8 | --EXPECTF-- 9 | Hello world 10 | 11 | Warning: hello() expects exactly 1 parameter, 0 given in %s on line %d 12 | -------------------------------------------------------------------------------- /manuscript/NewExtensionTests/tests/hello_with_parameter.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | hello() function - basic test for hello() with parameter 3 | --FILE-- 4 | 7 | --EXPECTF-- 8 | Hello world 9 | -------------------------------------------------------------------------------- /manuscript/NewExtensionTests/tests/hello_without_parameter.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | hello() function - basic test for hello() without parameter 3 | --FILE-- 4 | 7 | --EXPECTF-- 8 | Warning: hello() expects exactly 1 parameter, 0 given in %s on line %d 9 | -------------------------------------------------------------------------------- /manuscript/NewExtension_1/.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | *.lo 3 | *.la 4 | .libs 5 | acinclude.m4 6 | aclocal.m4 7 | autom4te.cache 8 | build 9 | config.guess 10 | config.h 11 | config.h.in 12 | config.log 13 | config.nice 14 | config.status 15 | config.sub 16 | configure 17 | configure.in 18 | include 19 | install-sh 20 | libtool 21 | ltmain.sh 22 | Makefile 23 | Makefile.fragments 24 | Makefile.global 25 | Makefile.objects 26 | missing 27 | mkinstalldirs 28 | modules 29 | run-tests.php 30 | tests/*/*.diff 31 | tests/*/*.out 32 | tests/*/*.php 33 | tests/*/*.exp 34 | tests/*/*.log 35 | tests/*/*.sh 36 | -------------------------------------------------------------------------------- /manuscript/NewExtension_1/config.m4: -------------------------------------------------------------------------------- 1 | dnl config.m4 2 | 3 | PHP_ARG_ENABLE(hello, whether to enable hello extension, 4 | [ --enable-hello Enable hello extension]) 5 | 6 | if test "$PHP_HELLO" != "no"; then 7 | PHP_NEW_EXTENSION(hello, hello.c, $ext_shared) 8 | fi 9 | 10 | -------------------------------------------------------------------------------- /manuscript/NewExtension_1/hello.c: -------------------------------------------------------------------------------- 1 | /* hello.c */ 2 | 3 | /* include PHP API */ 4 | #include "php.h" 5 | 6 | /* this module's header file */ 7 | #include "php_hello.h" 8 | 9 | /* provide info for the reflection API */ 10 | ZEND_BEGIN_ARG_INFO(arginfo_hello, 0) 11 | ZEND_END_ARG_INFO(); 12 | 13 | /* define the function we want to add */ 14 | zend_function_entry hello_functions[] = { 15 | PHP_FE(hello, arginfo_hello) 16 | PHP_FE_END 17 | }; 18 | 19 | /* "hello_functions" refers to the struct defined above */ 20 | zend_module_entry hello_module_entry = { 21 | STANDARD_MODULE_HEADER, 22 | PHP_HELLO_EXTNAME, 23 | hello_functions, /* Function entries */ 24 | NULL, /* Module init */ 25 | NULL, /* Module shutdown */ 26 | NULL, /* Request init */ 27 | NULL, /* Request shutdown */ 28 | NULL, /* Module information */ 29 | PHP_HELLO_VERSION, 30 | STANDARD_MODULE_PROPERTIES 31 | }; 32 | 33 | /* install module */ 34 | ZEND_GET_MODULE(hello) 35 | 36 | PHP_FUNCTION(hello) { 37 | /* php_printf is PHP's version of printf */ 38 | php_printf("Hello world!\n"); 39 | } 40 | -------------------------------------------------------------------------------- /manuscript/NewExtension_1/php_hello.h: -------------------------------------------------------------------------------- 1 | /* php_hello.h */ 2 | 3 | #define PHP_HELLO_EXTNAME "hello" 4 | #define PHP_HELLO_VERSION "0.1" 5 | 6 | extern zend_module_entry hello_module_entry; 7 | #define phpext_hello_ptr &check_hello_entry 8 | 9 | PHP_FUNCTION(hello); 10 | 11 | -------------------------------------------------------------------------------- /manuscript/NewExtension_2/.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | *.lo 3 | *.la 4 | .libs 5 | acinclude.m4 6 | aclocal.m4 7 | autom4te.cache 8 | build 9 | config.guess 10 | config.h 11 | config.h.in 12 | config.log 13 | config.nice 14 | config.status 15 | config.sub 16 | configure 17 | configure.in 18 | include 19 | install-sh 20 | libtool 21 | ltmain.sh 22 | Makefile 23 | Makefile.fragments 24 | Makefile.global 25 | Makefile.objects 26 | missing 27 | mkinstalldirs 28 | modules 29 | run-tests.php 30 | tests/*/*.diff 31 | tests/*/*.out 32 | tests/*/*.php 33 | tests/*/*.exp 34 | tests/*/*.log 35 | tests/*/*.sh 36 | -------------------------------------------------------------------------------- /manuscript/NewExtension_2/config.m4: -------------------------------------------------------------------------------- 1 | dnl config.m4 2 | 3 | PHP_ARG_ENABLE(hello, whether to enable hello extension, 4 | [ --enable-hello Enable hello extension]) 5 | 6 | if test "$PHP_HELLO" != "no"; then 7 | PHP_NEW_EXTENSION(hello, hello.c, $ext_shared) 8 | fi 9 | 10 | -------------------------------------------------------------------------------- /manuscript/NewExtension_2/hello.c: -------------------------------------------------------------------------------- 1 | /* hello.c */ 2 | 3 | /* include PHP API */ 4 | #include "php.h" 5 | 6 | /* this module's header file */ 7 | #include "php_hello.h" 8 | 9 | /* provide info for the reflection API */ 10 | ZEND_BEGIN_ARG_INFO(arginfo_hi_world, 0) 11 | ZEND_END_ARG_INFO(); 12 | ZEND_BEGIN_ARG_INFO(arginfo_bye_world, 0) 13 | ZEND_END_ARG_INFO(); 14 | 15 | /* define the function we want to add */ 16 | zend_function_entry hello_functions[] = { 17 | PHP_FE(hi_world, arginfo_hi_world) 18 | PHP_FE(bye_world, arginfo_bye_world) 19 | PHP_FE_END 20 | }; 21 | 22 | /* "hello_functions" refers to the struct defined above */ 23 | zend_module_entry hello_module_entry = { 24 | STANDARD_MODULE_HEADER, 25 | PHP_HELLO_EXTNAME, 26 | hello_functions, /* Function entries */ 27 | NULL, /* Module init */ 28 | NULL, /* Module shutdown */ 29 | NULL, /* Request init */ 30 | NULL, /* Request shutdown */ 31 | NULL, /* Module information */ 32 | PHP_HELLO_VERSION, 33 | STANDARD_MODULE_PROPERTIES 34 | }; 35 | 36 | /* install module */ 37 | ZEND_GET_MODULE(hello) 38 | 39 | PHP_FUNCTION(hi_world) { 40 | php_printf("Hello world!\n"); 41 | } 42 | 43 | PHP_FUNCTION(bye_world) { 44 | php_printf("Goodbye world!\n"); 45 | } 46 | -------------------------------------------------------------------------------- /manuscript/NewExtension_2/php_hello.h: -------------------------------------------------------------------------------- 1 | /* php_hello.h */ 2 | 3 | #define PHP_HELLO_EXTNAME "hello" 4 | #define PHP_HELLO_VERSION "0.1" 5 | 6 | extern zend_module_entry hello_module_entry; 7 | #define phpext_hello_ptr &check_hello_entry 8 | 9 | PHP_FUNCTION(hi_world); 10 | PHP_FUNCTION(bye_world); 11 | -------------------------------------------------------------------------------- /manuscript/NewObject.md: -------------------------------------------------------------------------------- 1 | # Objects 2 | 3 | Now let's see how objects are built. We'll build a ```Human``` object and 4 | put it in the ```Earth\Animal``` namespace. Declare a class entry and initialize 5 | it in the PHP_MINIT_FUNCTION. 6 | 7 | ```c 8 | zend_class_entry *php_animal_human_ce; 9 | 10 | PHP_MINIT_FUNCTION(hello) { 11 | REGISTER_INI_ENTRIES(); 12 | 13 | /* namespace Earth\Animal { class Human {} } */ 14 | zend_class_entry ce; 15 | INIT_CLASS_ENTRY(ce, ZEND_NS_NAME("Earth\\Animal", "Human"), NULL); 16 | php_animal_human_ce = zend_register_internal_class(&ce); 17 | 18 | return SUCCESS; 19 | } 20 | ``` 21 | 22 | Use the hello function that we already added as a method of the Human class. 23 | If you remember correctly, the function was added in ```hello_functions```. 24 | 25 | ```c 26 | PHP_MINIT_FUNCTION(hello) { 27 | REGISTER_INI_ENTRIES(); 28 | 29 | /* namespace Earth\Animal { class Human {} } */ 30 | zend_class_entry ce; 31 | INIT_CLASS_ENTRY(ce, ZEND_NS_NAME("Earth\\Animal", "Human"), hello_functions); 32 | php_animal_human_ce = zend_register_internal_class(&ce); 33 | 34 | return SUCCESS; 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /manuscript/NewObject/.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | *.lo 3 | *.la 4 | .libs 5 | acinclude.m4 6 | aclocal.m4 7 | autom4te.cache 8 | build 9 | config.guess 10 | config.h 11 | config.h.in 12 | config.log 13 | config.nice 14 | config.status 15 | config.sub 16 | configure 17 | configure.in 18 | include 19 | install-sh 20 | libtool 21 | ltmain.sh 22 | Makefile 23 | Makefile.fragments 24 | Makefile.global 25 | Makefile.objects 26 | missing 27 | mkinstalldirs 28 | modules 29 | run-tests.php 30 | tests/*/*.diff 31 | tests/*/*.out 32 | tests/*/*.php 33 | tests/*/*.exp 34 | tests/*/*.log 35 | tests/*/*.sh 36 | -------------------------------------------------------------------------------- /manuscript/NewObject/config.m4: -------------------------------------------------------------------------------- 1 | dnl config.m4 2 | 3 | PHP_ARG_ENABLE(hello, whether to enable hello extension, 4 | [ --enable-hello Enable hello extension]) 5 | 6 | if test "$PHP_HELLO" != "no"; then 7 | PHP_NEW_EXTENSION(hello, hello.c, $ext_shared) 8 | fi 9 | 10 | -------------------------------------------------------------------------------- /manuscript/NewObject/hello.c: -------------------------------------------------------------------------------- 1 | /* hello.c */ 2 | 3 | /* include PHP API */ 4 | #include "php.h" 5 | #include "php_ini.h" 6 | 7 | /* this module's header file */ 8 | #include "php_hello.h" 9 | 10 | /* provide info for the reflection API */ 11 | ZEND_BEGIN_ARG_INFO(arginfo_hello, 0) 12 | ZEND_ARG_INFO(0, name) 13 | ZEND_END_ARG_INFO(); 14 | 15 | /* define the function we want to add */ 16 | zend_function_entry hello_functions[] = { 17 | PHP_FE(hello, arginfo_hello) 18 | PHP_FE_END 19 | }; 20 | 21 | /* "hello_functions" refers to the struct defined above */ 22 | zend_module_entry hello_module_entry = { 23 | STANDARD_MODULE_HEADER, 24 | PHP_HELLO_EXTNAME, 25 | hello_functions, /* Function entries */ 26 | PHP_MINIT(hello), /* Module init */ 27 | PHP_MSHUTDOWN(hello), /* Module shutdown */ 28 | NULL, /* Request init */ 29 | NULL, /* Request shutdown */ 30 | NULL, /* Module information */ 31 | PHP_HELLO_VERSION, 32 | STANDARD_MODULE_PROPERTIES 33 | }; 34 | 35 | PHP_INI_BEGIN() 36 | PHP_INI_ENTRY("hello.yell", "0", PHP_INI_ALL, NULL) 37 | PHP_INI_END() 38 | 39 | zend_class_entry *php_animal_human_ce; 40 | 41 | PHP_MINIT_FUNCTION(hello) { 42 | REGISTER_INI_ENTRIES(); 43 | 44 | /* namespace Earth\Animal { class Human {} } */ 45 | zend_class_entry ce; 46 | INIT_CLASS_ENTRY(ce, ZEND_NS_NAME("Earth\\Animal", "Human"), hello_functions); 47 | php_animal_human_ce = zend_register_internal_class(&ce); 48 | 49 | return SUCCESS; 50 | } 51 | 52 | PHP_MSHUTDOWN_FUNCTION(hello) { 53 | UNREGISTER_INI_ENTRIES(); 54 | return SUCCESS; 55 | } 56 | 57 | /* install module */ 58 | ZEND_GET_MODULE(hello) 59 | 60 | /* function hello(string $name): bool */ 61 | PHP_FUNCTION(hello) { 62 | int i = 0; 63 | zend_string *name; 64 | 65 | ZEND_PARSE_PARAMETERS_START(1, 1) 66 | Z_PARAM_STR(name) 67 | ZEND_PARSE_PARAMETERS_END(); 68 | 69 | if (INI_BOOL("hello.yell") == 1) { 70 | while (name->val[i]) { 71 | name->val[i] = toupper(name->val[i]); 72 | i++; 73 | } 74 | php_printf("HELLO %s!\n", ZSTR_VAL(name)); 75 | } else { 76 | php_printf("Hello %s\n", ZSTR_VAL(name)); 77 | } 78 | 79 | RETURN_TRUE; 80 | } 81 | -------------------------------------------------------------------------------- /manuscript/NewObject/php_hello.h: -------------------------------------------------------------------------------- 1 | /* php_hello.h */ 2 | 3 | #define PHP_HELLO_EXTNAME "hello" 4 | #define PHP_HELLO_VERSION "0.1" 5 | 6 | extern zend_module_entry hello_module_entry; 7 | #define phpext_hello_ptr &check_hello_entry 8 | 9 | PHP_MINIT_FUNCTION(hello); 10 | PHP_MSHUTDOWN_FUNCTION(hello); 11 | 12 | PHP_FUNCTION(hello); 13 | -------------------------------------------------------------------------------- /manuscript/PreparePhp.md: -------------------------------------------------------------------------------- 1 | # Compile your own PHP 2 | 3 | ## Getting the source 4 | 5 | First get the source. Most Linux distribution provide a source package 6 | for the PHP version they package for you. Eg: 7 | 8 | - php7-dev on Debian 9 | - php-devel on CentOS 10 | 11 | If you want the latest PHP versions, they are probably not packaged by your 12 | distro yet. For Debian / Ubuntu there is https://deb.sury.org/ that you can 13 | add as DPA / PPA. This is maintained by Ondřej Surý, who is also an official 14 | package maintainer. For production, I always prefer packages. 15 | 16 | But, you can also get the source from Git and use that. I prefer this option 17 | to hack on my local machine. There is a GitHub mirror of the official PHP 18 | repository http://git.php.net/ 19 | 20 | ```bash 21 | git clone https://github.com/php/php-src.git 22 | cd php-src 23 | ``` 24 | 25 | Checkout the branch you want to work on. 26 | 27 | ```bash 28 | git checkout PHP-7.0 29 | git checkout PHP-7.0.8 30 | git checkout PHP-7.1 31 | git checkout master 32 | ``` 33 | 34 | If you are on master, this is bleeding edge. At this moment it points to the 35 | development version of PHP-7.2. The PHP-7.1 will point to the latest PHP 7.1 36 | release, with all patches that were merged after the release. If you want to 37 | use a released versions, checkout a specific branch, like PHP-7.1.2. 38 | 39 | ## Building a minimal version 40 | 41 | The minimal instructions to compile a working PHP version are: 42 | 43 | ```bash 44 | ./buildconf --force 45 | 46 | ./configure \ 47 | --enable-debug 48 | 49 | make 50 | ``` 51 | 52 | The buildconf script makes sure your directory is cleaned up and any leftovers 53 | of previous builds are removed. 54 | 55 | The ```--enable-debug``` is optional. You need it to be able to properly debug 56 | your extensions. When PHP is compiled with this flag, it will report memory leaks. 57 | The flag also makes sure no debug symbols are stripped out and the binaries are 58 | not optimized. With optimized binaries, tools like the GNU debugger (gdb) will 59 | have a difficult time trying to translate things back to what was your original 60 | code. 61 | 62 | > **Efficiency tip:** GNU ```make``` will use GCC to compile the code. There is 63 | a wrapper, called "colorgcc" that colorizes the GCC output. This brings error 64 | and warning messages to your attention in a better way. It is packaged on 65 | Ubuntu/Debian, When installed, GCC will automatically colorize it's output. 66 | 67 | The PHP binary will be in ```./sapi/cli/php```. 68 | 69 | You can run make in parallel if you want to use more CPU cores. Eg: ```make -j2``` 70 | for 2 using cores. Or use ```make -j`nproc` ``` as a shorthand. Note that this 71 | does not make your build process scale linearly. 72 | 73 | ## About ZTS 74 | 75 | Zend Thread Safety (ZTS) is a compile time configuration option. If you don't 76 | have it, your PHP will be Not Thread Safe (NTS). If you want people with a ZTS 77 | PHP version to be able to use your extension, you have to make your extension 78 | thread safe. 79 | 80 | Configure your PHP with ```--enable-maintainer-zts``` to build a ZTS version. 81 | This flag will enable thread safety checks. These checks will also be applied on 82 | your own extensions. 83 | 84 | Consider that you need ZTS when the pthreads (POSIX threads) extension is used, 85 | or when the webserver (Apache2 mpm-worker or IIS) uses PHP as a module. When 86 | FastCGI / FPM is used, or Apache2 mpm-prefork, you don't need ZTS because forking 87 | (new processes) is used over threads. 88 | 89 | NTS is the preferred way of running PHP. NTS also makes it a little bit easier 90 | to write and debug your extensions. 91 | 92 | You can check how your PHP was built with ```php -v```. It will show ZTS or NTS 93 | on the first line of output. In older PHP versions, if neither of these is 94 | displayed it will be NTS. 95 | 96 | ## Building a full version 97 | 98 | A minimal PHP version will be good to test PHP core, but will get you stuck 99 | pretty fast if you want to test a web framework with the latest PHP release. 100 | 101 | If you have never built PHP, how do you figure out what to include in your 102 | configure string? Easy. The ```php-config``` binary from php development package 103 | will display the ```--configure-options``` that were used. If you want to review 104 | all possible options, use ```./configure --help```. 105 | 106 | This is how I build my PHP, it's more or less what you get from your Linux distro. 107 | Dependencies you need on Debian 8 are: 108 | 109 | ```bash 110 | # Dependencies for building 111 | apt-get update 112 | apt-get install -y \ 113 | make \ 114 | autoconf \ 115 | gcc \ 116 | bison \ 117 | g++ 118 | 119 | # Dependencies for the selected extensions 120 | apt-get install -y \ 121 | libxml2-dev \ 122 | libbz2-dev \ 123 | libcurl4-openssl-dev \ 124 | libltdl-dev \ 125 | libpng12-dev \ 126 | libjpeg62-turbo-dev \ 127 | libfreetype6-dev \ 128 | libxpm-dev \ 129 | libimlib2-dev \ 130 | libicu-dev \ 131 | libreadline6-dev \ 132 | libxslt1-dev \ 133 | libssl-dev \ 134 | libsystemd-dev 135 | ``` 136 | 137 | Compile and install PHP CLI and FPM. I build them separately because I like to 138 | have different php.ini configurations for both of them. 139 | 140 | ```bash 141 | ./buildconf --force 142 | 143 | # Prepare installation directories 144 | 145 | mkdir -p /etc/php7.1/conf.d 146 | mkdir -p /etc/php7.1/{cli,fpm}/conf.d 147 | mkdir /usr/local/php7.1 148 | 149 | # Default options 150 | 151 | CONFIGURE_STRING="--prefix=/usr/local/php7.1 \ 152 | --enable-bcmath \ 153 | --with-bz2 \ 154 | --with-zlib \ 155 | --enable-zip \ 156 | --enable-calendar \ 157 | --enable-exif \ 158 | --enable-ftp \ 159 | --with-gettext \ 160 | --with-gd \ 161 | --with-jpeg-dir \ 162 | --with-png-dir \ 163 | --with-freetype-dir \ 164 | --with-xpm-dir \ 165 | --enable-mbstring \ 166 | --enable-mysqlnd \ 167 | --with-mysqli=mysqlnd \ 168 | --with-pdo-mysql=mysqlnd \ 169 | --with-openssl \ 170 | --enable-intl \ 171 | --enable-soap \ 172 | --with-readline \ 173 | --with-curl \ 174 | --with-xsl \ 175 | --disable-cgi" 176 | 177 | # Options for development 178 | 179 | CONFIGURE_STRING="$CONFIGURE_STRING \ 180 | --enable-debug" 181 | ``` 182 | 183 | This will get the source ready to compile CLI or FPM. The first one to compile 184 | is CLI: 185 | 186 | ```bash 187 | # Configure and compile CLI 188 | 189 | ./configure \ 190 | $CONFIGURE_STRING \ 191 | --enable-pcntl \ 192 | --with-config-file-path=/etc/php7.1/cli \ 193 | --with-config-file-scan-dir=/etc/php7.1/cli/conf.d 194 | 195 | make -j2 196 | make install 197 | 198 | # Install config files 199 | 200 | cp php.ini-production /etc/php7.1/cli/php.ini 201 | sed -i 's/;date.timezone =.*/date.timezone = Europe\/Brussels/' /etc/php7.1/cli/php.ini 202 | ``` 203 | 204 | Now would be the right time to run ```make test``` to help the PHP project. 205 | 206 | Next clean the build and do the same for FPM. 207 | 208 | Note that in this example, I use ```www-data``` as the user for FPM. This user 209 | is available if you install Nginx or Apache2 on Debian. You can set it to your 210 | own user for development. Using a separate user for each website is considered 211 | as a good security practice in production. You can configure users in the FPM 212 | pool configuration. The option in the configure script just set a default in the 213 | main FPM configuration. 214 | 215 | ```bash 216 | make distclean 217 | ./buildconf --force 218 | ``` 219 | 220 | ```bash 221 | ./configure \ 222 | $CONFIGURE_STRING \ 223 | --with-config-file-path=/etc/php7.1/fpm \ 224 | --with-config-file-scan-dir=/etc/php7.1/fpm/conf.d \ 225 | --disable-cli \ 226 | --enable-fpm \ 227 | --with-fpm-systemd \ 228 | --with-fpm-user=www-data \ 229 | --with-fpm-group=www-data 230 | 231 | make -j2 232 | make install 233 | 234 | # Install config files 235 | 236 | cp php.ini-production /etc/php7.1/fpm/php.ini 237 | sed -i 's/;date.timezone =.*/date.timezone = Europe\/Brussels/' /etc/php7.1/fpm/php.ini 238 | 239 | cp sapi/fpm/php-fpm.conf.in /etc/php7.1/fpm/php-fpm.conf 240 | sed -i 's#^include=.*/#include=/etc/php7.1/fpm/pool.d/#' /etc/php7.1/fpm/php-fpm.conf 241 | 242 | mkdir /etc/php7.1/fpm/pool.d/ 243 | cp /usr/local/php7.1/etc/php-fpm.d/www.conf.default /etc/php7.1/fpm/pool.d/www.conf 244 | sed -i 's/listen = 127.0.0.1:9000/listen = 127.0.0.1:9070/g' /etc/php7.1/fpm/pool.d/www.conf 245 | 246 | # Enable in systemd 247 | 248 | cat << EOF >/etc/systemd/system/php7.1-fpm.service 249 | [Unit] 250 | Description=The PHP FastCGI Process Manager 251 | After=syslog.target network.target 252 | 253 | [Service] 254 | Type=notify 255 | PIDFile=/var/run/php7.1-fpm.pid 256 | ExecStart=/usr/local/php7.1/sbin/php-fpm --nodaemonize --fpm-config /etc/php7.1/fpm/php-fpm.conf 257 | ExecReload=/bin/kill -USR2 $MAINPID 258 | PrivateTmp=yes 259 | 260 | [Install] 261 | WantedBy=multi-user.target 262 | EOF 263 | 264 | systemctl enable php7.1-fpm 265 | systemctl start php7.1-fpm 266 | ``` 267 | 268 | The PHP source also provides an init.d script, but almost all Linux distributions, 269 | including Debian and CentOS, use systemd these days. 270 | 271 | Note that FPM was built with the ```--with-fpm-systemd``` flag. This is to support 272 | the ```Type=notify``` in the systemd service. It will show more information with 273 | the ```systemctl status php7.1-fpm``` command. 274 | 275 | Now you are ready to use PHP from ```/usr/local/php7.1/bin/php```. You can symlink 276 | the binaries ```php```, ```phpize```, ```php-config```, ... to a directory in 277 | your path or just add the entire directory to your path. 278 | 279 | ```bash 280 | export PATH=$PATH:/usr/local/php7.1/bin:/usr/local/php7.1/sbin 281 | ``` 282 | 283 | It's easy to switch to a different PHP version by changing the PATH variable. 284 | I added a complete script to build PHP and a script to switch versions in 285 | "Appendix A" of this book. 286 | 287 | ## Building extensions 288 | 289 | You can now build extra extensions if you like. One extension, opcache, is already 290 | built and you will want to enable this for performance reasons. 291 | 292 | ```bash 293 | echo "zend_extension=opcache.so" > /etc/php7.1/conf.d/opcache.ini 294 | ln -s /etc/php7.1/conf.d/opcache.ini /etc/php7.1/cli/conf.d/opcache.ini 295 | ln -s /etc/php7.1/conf.d/opcache.ini /etc/php7.1/fpm/conf.d/opcache.ini 296 | ``` 297 | 298 | The PHP extensions in the ```ext``` directory can be built into PHP or built as 299 | a module. Modules, like opcache, are loaded separately. If you want to add snmp 300 | support to your PHP installation. You don't have to build everything from source 301 | again. Just build the snmp extension and load it as a module. 302 | 303 | ```bash 304 | cd ext/snmp 305 | phpize 306 | ./configure 307 | make 308 | make install 309 | ``` 310 | 311 | The opcache extension is a special one. It is a Zend module. There are not many 312 | Zend modules. Regular modules are loaded with ```extension=...```. 313 | 314 | ```bash 315 | echo "extension=snmp.so" > /etc/php7.1/conf.d/snmp.ini 316 | ln -s /etc/php7.1/conf.d/snmp.ini /etc/php7.1/cli/conf.d/snmp.ini 317 | ln -s /etc/php7.1/conf.d/snmp.ini /etc/php7.1/fpm/conf.d/snmp.ini 318 | ``` 319 | 320 | Check if the module gets loaded correctly: 321 | 322 | ```bash 323 | php -m | grep snmp 324 | snmp 325 | ``` 326 | 327 | If you need a module that is not provided by PHP in the ```ext``` directory, 328 | you can go to pecl at https://pecl.php.net/ to find a module that is maintained 329 | by the community. For example, modules to talk to MongoDB, Memcached, or some 330 | other database. Modules to work with SSH, geoIP, etc. 331 | 332 | These pecl modules can be built the same way as the modules that are provided by 333 | PHP. Read the documentation for each module to understand how to use it. 334 | 335 | There is an installer called pear at https://pear.php.net/ that can download, 336 | compile and install pecl modules for you. Pear also hosts PHP libraries that are 337 | written in PHP. Pecl only has extensions that are written in C. 338 | 339 | ## IDE support 340 | 341 | Eclipse CDT (C/C++ Development Tooling) [https://eclipse.org/cdt/](https://eclipse.org/cdt/) 342 | will make your life a lot easier. Use it to work on PHP core and extensions. 343 | 344 | For example: PHP makes heavily use of macro's. Eclipse CDT has macro expansion functionality. 345 | 346 | Normally you develop your own extensions outside of the PHP scr or ext directory. 347 | This is how you set up an Eclipse CDT project to make it work with your PHP src installation. 348 | 349 | ``` 350 | File -> New -> Makefile Project with Existing Code 351 | ``` 352 | 353 | Set a Project Name and Browse to your existing Code Location. 354 | 355 | ``` 356 | Right click your project -> Configure -> Configure and Detect Nested Projects 357 | ``` 358 | 359 | Add the location of your local PHP source folder. 360 | 361 | Build your extension via Eclipse CDT. 362 | 363 | ``` 364 | Right click your project -> Build Project 365 | ``` 366 | 367 | After that, all references like php.h, macro expansion etc. will work. 368 | -------------------------------------------------------------------------------- /manuscript/References.md: -------------------------------------------------------------------------------- 1 | # References 2 | 3 | ## Recent material, covering PHP 7 4 | 5 | - PHP 7 extensions workshop, slides http://www.slideshare.net/jpauli/php7-extensions-workshop 6 | - PHP 7 extensions workshop, code https://github.com/jpauli/PHP_Extension_Workshop/ 7 | - PHP 7 data structures http://www.slideshare.net/patrick.allaert/php-data-structures-and-the-impact-of-php-7-on-them-php-days-2015 8 | - PHP 7 what changed internally http://www.slideshare.net/nikita_ppv/php-7-what-changed-internally 9 | - PHP 7 is coming http://www.slideshare.net/jpauli/php7-is-coming 10 | - Huge Page usage in PHP 7 http://jpauli.github.io/2015/10/28/huge-page.html 11 | - Internal value representation in PHP 7, part 1 https://nikic.github.io/2015/05/05/Internal-value-representation-in-PHP-7-part-1.html 12 | - Internal value representation in PHP 7, part 2 https://nikic.github.io/2015/06/19/Internal-value-representation-in-PHP-7-part-2.html 13 | - PHP's new hashtable implementation https://nikic.github.io/2014/12/22/PHPs-new-hashtable-implementation.html 14 | - Deep Dive into PHP 7 Internal Changes https://drive.google.com/file/d/0B3UKOMH_4lgBaHp4YW91RXVzaTA/view 15 | - Speeding up the Web with PHP 7 https://www.youtube.com/watch?v=ZPvC_HGNqQE 16 | - Hacking on PHP 7 with Joe Watkins, part 1 https://youtu.be/_arORgEVl-M 17 | - Hacking on PHP 7 with Joe Watkins, part 2 https://youtu.be/f9N-v-JUbTQ 18 | 19 | ## Other material 20 | 21 | - http://www.phpinternalsbook.com/ 22 | - http://devzone.zend.com/303/extension-writing-part-i-introduction-to-php-and-zend/ 23 | - http://devzone.zend.com/317/extension-writing-part-ii-parameters-arrays-and-zvals/ 24 | - http://devzone.zend.com/318/extension-writing-part-ii-parameters-arrays-and-zvals-continued/ 25 | - http://devzone.zend.com/446/extension-writing-part-iii-resources/ 26 | - http://ftp.osuosl.org/pub/hiphop/resources/conf/2013/ConFoo-PHPExtensions.pdf 27 | - http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html 28 | - https://nikic.github.io/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html 29 | - http://fr.slideshare.net/jpauli/understanding-php-objects 30 | - http://fr.slideshare.net/jpauli/understanding-php-memory 31 | - http://php.net/manual/en/internals2.ze1.zendapi.php 32 | - https://wiki.php.net/phpng-upgrading 33 | - http://externals.io/ 34 | - https://github.com/beberlei/whitewashing.de/blob/master/drafts/porting_extension_to_php7.rst 35 | 36 | And, not to forget the "Extending and embedding PHP" book from Sara Golemon. 37 | -------------------------------------------------------------------------------- /manuscript/WritingTests.md: -------------------------------------------------------------------------------- 1 | # Writing tests 2 | 3 | ## Your first test 4 | 5 | PHP is tested with PHPT files. These are files that contain PHP code to check if 6 | functionality behaves as expected. See http://qa.php.net/write-test.php for more info. 7 | 8 | Let's write a test for our hello function in the hello extension. At this point 9 | the function is defined like this: 10 | 11 | ```php 12 | function hello(string $name): bool; 13 | ``` 14 | 15 | Create a file ```tests/hello.phpt``` with this content: 16 | 17 | ```text 18 | --TEST-- 19 | hello() function - basic test for hello() 20 | --FILE-- 21 | 24 | --EXPECT-- 25 | Hello world 26 | ``` 27 | 28 | To run the test just type ```make test```. It will run all tests and print a report: 29 | 30 | ```text 31 | --------------------------------------------------------------------- 32 | Number of tests : 1 1 33 | Tests skipped : 0 ( 0.0%) -------- 34 | Tests warned : 0 ( 0.0%) ( 0.0%) 35 | Tests failed : 0 ( 0.0%) ( 0.0%) 36 | Expected fail : 0 ( 0.0%) ( 0.0%) 37 | Tests passed : 1 (100.0%) (100.0%) 38 | --------------------------------------------------------------------- 39 | ``` 40 | 41 | If a test would fail, the test results will be dumped in the directory of the test 42 | file. There will be a lot of information: 43 | 44 | - hello.log The test log 45 | - hello.exp What was expected 46 | - hello.out The actual result 47 | - hello.diff The difference between the expected and actual result 48 | - hello.php A PHP snippet of the failing test 49 | - hello.sh A script to run the PHP snippet of the failing test 50 | 51 | ## More tests 52 | 53 | You can test more than one case in a PHPT file, or you can create multiple PHPT 54 | files for different features. 55 | 56 | ```text 57 | --TEST-- 58 | hello() function - basic test for hello() 59 | --FILE-- 60 | 64 | --EXPECTF-- 65 | Hello world 66 | 67 | Warning: hello() expects exactly 1 parameter, 0 given in %s on line %d 68 | ``` 69 | 70 | Note that in the first test example I used ```EXPECT``` and in the second I 71 | use ```EXPECTF``` to support the ```%s``` and ```%d``` variables. 72 | 73 | You can get a lot of examples of how to write test cases from the ext directory 74 | of PHP. 75 | 76 | If you ever encounter a bug in PHP or an extension, you have to write a specific 77 | test for it. Put the ID of the bug in the name of the text, eg: bug12345.phpt. 78 | This makes it easy for the developers to reproduce the bug and all future versions 79 | of PHP or the extension will keep test for this specific bug, making sure it will 80 | never happen again. 81 | 82 | If you don't want the script to ask "Do you want to send this report now? [Yns]:" 83 | run it with ```NO_INTERACTION``` set. Behind the scenes the ```run-tests.php``` 84 | that is in the root of the php-scr directory will be used for make test. Look at 85 | that file for all details. 86 | 87 | ```bash 88 | NO_INTERACTION=1 make test 89 | ``` 90 | 91 | If you have multiple tests but only want to run one or some of them, you can 92 | specify the test like this: 93 | 94 | ```bash 95 | make test TESTS=tests/hello_with_parameter.phpt 96 | ``` 97 | 98 | If you want to run the tests for php-src, you can use ```make test``` or use 99 | the script ```run-tests.php``` in the root of the repository. This script also 100 | accepts a parameter if you don't want to run all tests. 101 | 102 | ```bash 103 | run-tests.php tests/basic/001.phpt 104 | ``` 105 | 106 | Note that you have to set the environment variable ```TEST_PHP_EXECUTABLE``` to 107 | be able to use ```run-tests.php```. 108 | 109 | ```bash 110 | export TEST_PHP_EXECUTABLE=$(which php) 111 | ``` 112 | -------------------------------------------------------------------------------- /manuscript/images/title_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tvlooy/php-ext-dev-book/0950155b57f5c1e73c7542b535841aa053f5e37e/manuscript/images/title_page.png --------------------------------------------------------------------------------