├── exceptions.rst ├── README.rst ├── debugging.rst ├── versioning.rst ├── add-class-storage.rst ├── compiling.rst ├── LICENSE.rst └── classes.rst /exceptions.rst: -------------------------------------------------------------------------------- 1 | Exceptions 2 | ========== 3 | 4 | Throwing a Predefined Exception From C++ Land 5 | --------------------------------------------- 6 | 7 | To throw an (SPL) InvalidArgumentException, you can add the following snippet 8 | to your code:: 9 | 10 | throw Object(SystemLib::AllocInvalidArgumentExceptionObject("Invalid tagSet")); 11 | 12 | There are similar predefined methods for other standard exceptions: 13 | 14 | Exception 15 | ``AllocExceptionObject`` 16 | 17 | BadMethodCallException (Spl) 18 | ``AllocBadMethodCallExceptionObject`` 19 | 20 | RuntimeException (Spl) 21 | ``AllocRuntimeExceptionObject`` 22 | 23 | OutOfBoundsException (Spl) 24 | ``AllocOutOfBoundsExceptionObject`` 25 | 26 | InvalidOperationException (Spl) 27 | ``AllocInvalidOperationExceptionObject`` 28 | 29 | DOMException (Spl) 30 | ``AllocDOMExceptionObject`` 31 | 32 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | HHVM/HNI Extensions Cookbook 2 | ============================ 3 | 4 | This repository contains a set of recipes on how to do specific tasks with 5 | HHVM/HNI. Each of the recipes were found out during the development of the 6 | `official HHVM driver for MongoDB`_ (currently in the prototype phase). 7 | 8 | Feel free to provide pull requests with recipes of our own, or pull requests 9 | to fix and/or improve existing recipes. 10 | 11 | This cookbook is released under a `CC0 1.0 Universal`_ license. 12 | 13 | .. _`official HHVM driver for MongoDB`: https://github.com/10gen-labs/mongo-hhvm-driver-prototype 14 | .. _`CC0 1.0 Universal`: LICENSE.rst 15 | 16 | Contents 17 | -------- 18 | 19 | - `Classes`_ 20 | - `Adding Resources to Classes`_ 21 | - `Dealing with Exceptions`_ 22 | - `Compiling and Setting up for Debugging`_ 23 | - `Debugging`_ 24 | 25 | .. _`Classes`: classes.rst 26 | .. _`Adding Resources to Classes`: add-class-storage.rst 27 | .. _`Dealing with Exceptions`: exceptions.rst 28 | .. _`Compiling and Setting up for Debugging`: compiling.rst 29 | .. _`Debugging`: debugging.rst 30 | -------------------------------------------------------------------------------- /debugging.rst: -------------------------------------------------------------------------------- 1 | Debugging 2 | ========= 3 | 4 | A few tricks for debugging structures and related things. 5 | 6 | Value of String objects 7 | ----------------------- 8 | 9 | In GDB, you can print the value of a String object with:: 10 | 11 | p *named_class.m_str.m_px 12 | 13 | p ((StringData*) 0x7fffe3c06000).m_data 14 | 15 | Value of Array objects 16 | ---------------------- 17 | 18 | :: 19 | 20 | p *v.m_data.parr 21 | 22 | p ((MixedArray::Elm*)((MixedArray*)&$10 + 1))[0]@2 23 | 24 | 25 | GDB utilities 26 | ------------- 27 | 28 | In ``./hphp/tools/gdb/*.py`` you can find a lot of GDB helper utilities. You 29 | can source these in GDB by running:: 30 | 31 | source path/hphp/toold//gdb/hhvm.py 32 | 33 | You can then easily print f.e. a string value with:: 34 | 35 | p named_class 36 | 37 | Instead of:: 38 | 39 | (gdb) p named_class 40 | $1 = {m_str = {m_px = 0x7fffe0429fd0}, static MinPrecomputedInteger = -128, 41 | static MaxPrecomputedInteger = 3967, static converted_integers_raw = 0x7fffe849d000, 42 | static converted_integers = 0x7fffe849d400, static integer_string_data_map = 43 | {, std::allocator > >> = std::unordered_map with 0 elements, 46 | }, static npos = -1} 47 | 48 | You will see:: 49 | 50 | (gdb) p named_class 51 | $2 = (HPHP::req::ptr) 0x7fffe0429fd0 "FallbackClass" 52 | 53 | -------------------------------------------------------------------------------- /versioning.rst: -------------------------------------------------------------------------------- 1 | Versioning 2 | ========== 3 | 4 | This section lists changes in APIs between HHVM versions (3.8 and later), and 5 | shows how to test for specific versions. 6 | 7 | 8 | Checking for HHVM versions 9 | -------------------------- 10 | 11 | HHVM defines a ``HHVM_VERSION_ID`` constant that seems like PHP's 12 | ``PHP_VERSION_ID``. Although it can be used for the same purpose, it does not 13 | follow the ``VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH`` that 14 | PHP follows. Instead, it uses ``VERSION_MAJOR * 65536 + VERSION_MINOR * 256 + 15 | VERSION_PATCH``. This makes testing for HHVM version 3.8 or higher, look like:: 16 | 17 | #if HHVM_VERSION_ID >= 198656 18 | 19 | This is not very readable. 20 | 21 | HHVM does define a *userland* ``HHVM_VERSION_ID`` constant, with the PHP way of 22 | calculating it. This means that:: 23 | 24 | 27 | 28 | Will echo ``30800``. 29 | 30 | As this is something we really want to replicate in an extension, it makes 31 | sense to add the following define to your main header file:: 32 | 33 | #define HIPPO_HHVM_VERSION (HHVM_VERSION_MAJOR * 10000 + HHVM_VERSION_MINOR * 100 + HHVM_VERSION_PATCH) 34 | 35 | And then you can check for versions with:: 36 | 37 | #if HIPPO_HHVM_VERSION >= 30900 38 | 39 | Changes between HHVM 3.8 and HHVM 3.9 40 | ------------------------------------- 41 | 42 | makeSmartPtr 43 | ~~~~~~~~~~~~ 44 | 45 | ``makeSmartPtr`` has gone away. Instead you now need to use ``req::make``. The 46 | following allows you to make your extension work with both variants:: 47 | 48 | req::ptr m_dt; 49 | 50 | #if HIPPO_HHVM_VERSION >= 30900 51 | m_dt = req::make(0, false); 52 | #else 53 | m_dt = makeSmartPtr(0, false); 54 | #endif 55 | -------------------------------------------------------------------------------- /add-class-storage.rst: -------------------------------------------------------------------------------- 1 | Adding class storage to store C-structs 2 | ======================================= 3 | 4 | This explains how to store C-based structures with your HHVM Native classes — 5 | akin to Zend Engine's "internal" member of the object structure. 6 | 7 | Definitions 8 | ----------- 9 | 10 | For each class, you need to implement a corresponding ``Data`` class. For 11 | example if your class is ``MongoDBManager``, then you define a 12 | ``MongoDBManagerData`` class in the ``HPHP`` namespace:: 13 | 14 | class MongoDBManagerData 15 | { 16 | public: 17 | static Class* s_class; 18 | static const StaticString s_className; 19 | 20 | /* Your own members */ 21 | 22 | static Class* getClass(); 23 | 24 | void sweep() { 25 | /* Here you destroy your resources */ 26 | }; 27 | 28 | ~MongoDBManagerData() { 29 | sweep(); 30 | } 31 | }; 32 | 33 | The ``sweep()`` method is called at the end of the request if the Data class 34 | has not gone out of scope yet, and the destructor is called when (of course), 35 | this Data object goes out of scope. 36 | 37 | To initialise the class, you also need to do:: 38 | 39 | Class* MongoDBManagerData::s_class = nullptr; 40 | const StaticString MongoDBManagerData::s_className("MongoDBManager"); 41 | 42 | And implement the ``getClass()`` method. As every ``getClass()`` method is the 43 | same, you can use the following macro instead:: 44 | 45 | #define IMPLEMENT_GET_CLASS(cls) \ 46 | Class* cls::getClass() { \ 47 | if (s_class == nullptr) { \ 48 | s_class = Unit::lookupClass(s_className.get()); \ 49 | assert(s_class); \ 50 | } \ 51 | return s_class; \ 52 | } 53 | 54 | And then call it like:: 55 | 56 | IMPLEMENT_GET_CLASS(MongoDBManagerData); 57 | 58 | Registering 59 | =========== 60 | 61 | In the ``moduleInit`` of your extension, you need to register this 62 | ``NativeData`` class with:: 63 | 64 | Native::registerNativeDataInfo(MongoDBManagerData::s_className.get()); 65 | 66 | In the class definition (in ``ext_mongodb.php``) you would also need the 67 | ``_NativeData`` strata before your class name:: 68 | 69 | <<__NativeData("MongoDBManager")>> 70 | class Manager { 71 | <<__Native>> 72 | function __construct(string $dsn = "localhost", array $options = array(), array $driverOptions = array()): void; 73 | } 74 | 75 | **Warning:** If you forget the ``<<__NativeData("MongoDBManager")>>`` 76 | annotation for the class, you are going to be in for a long frustrating ride 77 | figuring out why data is suddenly changing. 78 | -------------------------------------------------------------------------------- /compiling.rst: -------------------------------------------------------------------------------- 1 | Compiling 2 | ========= 3 | 4 | In order to make debugging work, and compiling faster, there are a few things 5 | that you need to configure. 6 | 7 | ccache 8 | ------ 9 | 10 | Repeatedly compiling HHVM can benefit enormously from having ccache_ enabled. 11 | It was a bit tricky to get going, so hence I am documenting it. 12 | 13 | First of all, install ccache_ (through ``apt-get``) or something. 14 | Then, when running ``cmake`` for HHVM, add a few flags:: 15 | 16 | cmake \ 17 | -D CMAKE_CXX_COMPILER="/usr/lib/ccache/g++" \ 18 | -D CMAKE_C_COMPILER="/usr/lib/ccache/gcc" \ 19 | . 20 | 21 | If you get strange errors, remove ``CMakeCache.txt`` first. The above will 22 | instruct ``cmake`` to use ``ccache`` as the compiler. It does not support ASM 23 | compilation caching, so we need to set that to the standard compiler. 24 | 25 | With ccache enabled, a ``make clean && make -j5`` now takes about 90 seconds, 26 | instead of nearly two hours. 27 | 28 | .. _ccache: https://ccache.samba.org/ 29 | 30 | Debug Builds of HHVM 31 | -------------------- 32 | 33 | In order to make a debug build, you need to pass yet another flag to 34 | ``cmake``:: 35 | 36 | cmake \ 37 | -DCMAKE_BUILD_TYPE=Debug \ 38 | . 39 | 40 | This new flag can be combined with the flags from the ccache section to create 41 | a debug build of HHVM. This sets the ``-Og`` flag instead of ``-O3`` among 42 | other things. 43 | 44 | Install Prefix 45 | -------------- 46 | 47 | You can set an install prefix by running cmake with a flag to ``cmake`` too:: 48 | 49 | cmake \ 50 | -DCMAKE_INSTALL_PREFIX=/usr/local/hhvm/3.12 \ 51 | . 52 | 53 | Default php.ini File Location 54 | ----------------------------- 55 | 56 | Since HHVM 3.10, you can specify the default location for the ``php.ini`` 57 | file. This is handy if you build/run more than one version of HHVM. The flag 58 | is:: 59 | 60 | cmake \ 61 | -DDEFAULT_CONFIG_DIR=/etc/hhvm/3.12 \ 62 | . 63 | 64 | Full Build 65 | ---------- 66 | 67 | With all the flags, the ``cmake`` incantation is:: 68 | 69 | cmake \ 70 | -DCMAKE_INSTALL_PREFIX=/usr/local/hhvm/3.12 \ 71 | -DCMAKE_CXX_COMPILER="/usr/lib/ccache/g++" \ 72 | -DCMAKE_C_COMPILER="/usr/lib/ccache/gcc" \ 73 | -DCMAKE_BUILD_TYPE=Debug \ 74 | . 75 | 76 | Debug Builds of HHVM extensions 77 | ------------------------------- 78 | 79 | Sadly, this still does not create easy to debug builds - especially when you 80 | are making an HHVM extension. I had to modify the 81 | ``CMakeFiles/mongodb.dir/flags.make`` file **after** running ``hphpize``. I 82 | changed the ``-Og`` to ``-O0 -ggdb3``. After compilation, I now have a debug 83 | build that works well with ``valgrind`` and ``gdb``. 84 | 85 | Please note, that you have to do this every single time after you have run 86 | ``hphpize``. 87 | 88 | An alternative, is to modify the installed 89 | ``/usr/local/lib/hhvm/CMake/HPHPCompiler.cmake`` and (in vim) run:: 90 | 91 | :%s/"-Og -g"/"" 92 | 93 | When this is done, you can run cmake in the extension's directory (after 94 | ``hphpize``):: 95 | 96 | cmake \ 97 | -DCMAKE_C_FLAGS="-O0 -ggdb3" \ 98 | -DCMAKE_CXX_FLAGS="-O0 -ggdb3" \ 99 | . 100 | 101 | Verbose Builds 102 | -------------- 103 | 104 | In order to see what ``make`` actually runs, you can invoke it with verbosity 105 | turned on:: 106 | 107 | make -j5 VERBOSE=1 108 | 109 | This then shows the full command to compile each file, and is useful for 110 | figuring out if things go wrong, and whether specific compiler flags are set. 111 | 112 | HHVM make errors 113 | ---------------- 114 | 115 | Once in a while, ocaml gets confused about compiled files -- for example when 116 | a file is renamed. The error looks like something like:: 117 | 118 | [ 46%] Built target hphp_analysis 119 | Finished, 14 targets (14 cached) in 00:00:00. 120 | + /usr/bin/ocamlc.opt -c -g -w A -warn-error A -w -27 -w -4-6-29-35-44-48 -I server -I dfind -I utils -I format -I stubs -I socket -I procs -I parsing -I hhi -I h2tp -I typing -I fsnotify_linux -I naming -I search -I client -I globals -I deps -I heap -I h2tp/test -I h2tp/unparser -I h2tp/mapper -I h2tp/common -I third-party/inotify -I third-party/avl -I third-party/core -o server/serverConfig.cmi server/serverConfig.mli 121 | File "server/serverConfig.mli", line 1: 122 | Error: The files utils/relative_path.cmi and utils/typecheckerOptions.cmi 123 | make inconsistent assumptions over interface Utils 124 | Command exited with code 2. 125 | Compilation unsuccessful after building 84 targets (83 cached) in 00:00:00. 126 | Makefile:118: recipe for target '_build/hh_server.native' failed 127 | make[3]: *** [_build/hh_server.native] Error 10 128 | hphp/hack/CMakeFiles/hack.dir/build.make:49: recipe for target 'hphp/hack/CMakeFiles/hack' failed 129 | make[2]: *** [hphp/hack/CMakeFiles/hack] Error 2 130 | CMakeFiles/Makefile2:1226: recipe for target 'hphp/hack/CMakeFiles/hack.dir/all' failed 131 | make[1]: *** [hphp/hack/CMakeFiles/hack.dir/all] Error 2 132 | Makefile:116: recipe for target 'all' failed 133 | make: *** [all] Error 2 134 | 135 | You can fix this, by running:: 136 | 137 | git clean -fdx hphp/hack/ 138 | 139 | And rebuild. 140 | 141 | HHVM and GCC 5 142 | -------------- 143 | 144 | These don't work well together yet, instead, you need to compile with:: 145 | 146 | cmake \ 147 | -DDEFAULT_CONFIG_DIR=/etc/hhvm/3.12 \ 148 | -DCMAKE_BUILD_TYPE=Debug \ 149 | -DCMAKE_CXX_COMPILER=`which g++-4.9` \ 150 | -DCMAKE_C_COMPILER=`which gcc-4.9` \ 151 | -DCMAKE_ASM_COMPILER=`which gcc-4.9` \ 152 | -DCMAKE_INSTALL_PREFIX=/usr/local/hhvm/3.12.0 \ 153 | . 154 | 155 | This is not all though, you also need special versions of Boost and Google 156 | Log, if you're using a really new set of Debian packages. There is extra 157 | information at http://derickrethans.nl/hhvm-gcc-52.html 158 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | ================= 3 | 4 | Statement of Purpose 5 | -------------------- 6 | 7 | The laws of most jurisdictions throughout the world automatically confer 8 | exclusive Copyright and Related Rights (defined below) upon the creator and 9 | subsequent owner(s) (each and all, an "owner") of an original work of 10 | authorship and/or a database (each, a "Work"). 11 | 12 | Certain owners wish to permanently relinquish those rights to a Work for the 13 | purpose of contributing to a commons of creative, cultural and scientific works 14 | ("Commons") that the public can reliably and without fear of later claims of 15 | infringement build upon, modify, incorporate in other works, reuse and 16 | redistribute as freely as possible in any form whatsoever and for any purposes, 17 | including without limitation commercial purposes. These owners may contribute 18 | to the Commons to promote the ideal of a free culture and the further 19 | production of creative, cultural and scientific works, or to gain reputation or 20 | greater distribution for their Work in part through the use and efforts of 21 | others. 22 | 23 | For these and/or other purposes and motivations, and without any expectation of 24 | additional consideration or compensation, the person associating CC0 with a 25 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 26 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and 27 | publicly distribute the Work under its terms, with knowledge of his or her 28 | Copyright and Related Rights in the Work and the meaning and intended legal 29 | effect of CC0 on those rights. 30 | 31 | 1. Copyright and Related Rights. 32 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 33 | 34 | A Work made available under CC0 may be protected by copyright and related or 35 | neighboring rights ("Copyright and Related Rights"). Copyright and Related 36 | Rights include, but are not limited to, the following: 37 | 38 | - the right to reproduce, adapt, distribute, perform, display, communicate, 39 | and translate a Work; 40 | - moral rights retained by the original author(s) and/or performer(s); 41 | - publicity and privacy rights pertaining to a person's image or likeness 42 | depicted in a Work; 43 | - rights protecting against unfair competition in regards to a Work, 44 | subject to the limitations in paragraph 4(a), below; 45 | - rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | - database rights (such as those arising under Directive 96/9/EC of the 48 | European Parliament and of the Council of 11 March 1996 on the legal 49 | protection of databases, and under any national implementation thereof, 50 | including any amended or successor version of such directive); and 51 | - other similar, equivalent or corresponding rights throughout the world 52 | based on applicable law or treaty, and any national implementations 53 | thereof. 54 | 55 | 2. Waiver. 56 | ~~~~~~~~~~ 57 | 58 | To the greatest extent permitted by, but not in contravention of, applicable 59 | law, Affirmer hereby overtly, fully, permanently, irrevocably and 60 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 61 | and Related Rights and associated claims and causes of action, whether now 62 | known or unknown (including existing as well as future claims and causes of 63 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 64 | duration provided by applicable law or treaty (including future time 65 | extensions), (iii) in any current or future medium and for any number of 66 | copies, and (iv) for any purpose whatsoever, including without limitation 67 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 68 | the Waiver for the benefit of each member of the public at large and to the 69 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 70 | shall not be subject to revocation, rescission, cancellation, termination, or 71 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 72 | by the public as contemplated by Affirmer's express Statement of Purpose. 73 | 74 | 3. Public License Fallback. 75 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 76 | 77 | Should any part of the Waiver for any reason be judged legally invalid or 78 | ineffective under applicable law, then the Waiver shall be preserved to the 79 | maximum extent permitted taking into account Affirmer's express Statement of 80 | Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby 81 | grants to each affected person a royalty-free, non transferable, non 82 | sublicensable, non exclusive, irrevocable and unconditional license to exercise 83 | Affirmer's Copyright and Related Rights in the Work (i) in all territories 84 | worldwide, (ii) for the maximum duration provided by applicable law or treaty 85 | (including future time extensions), (iii) in any current or future medium and 86 | for any number of copies, and (iv) for any purpose whatsoever, including 87 | without limitation commercial, advertising or promotional purposes (the 88 | "License"). The License shall be deemed effective as of the date CC0 was 89 | applied by Affirmer to the Work. Should any part of the License for any reason 90 | be judged legally invalid or ineffective under applicable law, such partial 91 | invalidity or ineffectiveness shall not invalidate the remainder of the 92 | License, and in such case Affirmer hereby affirms that he or she will not (i) 93 | exercise any of his or her remaining Copyright and Related Rights in the Work 94 | or (ii) assert any associated claims and causes of action with respect to the 95 | Work, in either case contrary to Affirmer's express Statement of Purpose. 96 | 97 | 4. Limitations and Disclaimers. 98 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 99 | 100 | - No trademark or patent rights held by Affirmer are waived, abandoned, 101 | surrendered, licensed or otherwise affected by this document. 102 | - Affirmer offers the Work as-is and makes no representations or warranties 103 | of any kind concerning the Work, express, implied, statutory or 104 | otherwise, including without limitation warranties of title, 105 | merchantability, fitness for a particular purpose, non infringement, or 106 | the absence of latent or other defects, accuracy, or the present or 107 | absence of errors, whether or not discoverable, all to the greatest 108 | extent permissible under applicable law. 109 | - Affirmer disclaims responsibility for clearing rights of other persons 110 | that may apply to the Work or any use thereof, including without 111 | limitation any person's Copyright and Related Rights in the Work. 112 | Further, Affirmer disclaims responsibility for obtaining any necessary 113 | consents, permissions or other rights required for any use of the Work. 114 | - Affirmer understands and acknowledges that Creative Commons is not a 115 | party to this document and has no duty or obligation with respect to this 116 | CC0 or use of the Work. 117 | 118 | -------------------------------------------------------------------------------- /classes.rst: -------------------------------------------------------------------------------- 1 | Classes and Properties 2 | ====================== 3 | 4 | Instantiating Classes 5 | --------------------- 6 | 7 | If you need to instantiate a class, you can do that through 8 | ``Object{}``. It wants a ``Class*`` as argument, which you can 9 | obtain by calling ``Unit::lookupClass()``. 10 | 11 | In full, you'd have to do something like:: 12 | 13 | const StaticString s_MongoDriverWriteResult_className("MongoDB\\Driver\\WriteResult"); 14 | 15 | ... 16 | 17 | { 18 | static Class* c_foobar; 19 | ... 20 | 21 | c_foobar = Unit::lookupClass(s_MongoDriverWriteResult_className.get()); 22 | Object obj = Object{c_foobar}; 23 | 24 | return obj; 25 | } 26 | 27 | This snippet also returns the newly instantiated object. Make sure you get the 28 | name of the class right, and that you don't get a NULL pointer back from 29 | ``lookupClass``, or otherwise you get weird errors back. In my case, a "can 30 | not convert array to string" warning. 31 | 32 | Autoloading Classes 33 | ~~~~~~~~~~~~~~~~~~~ 34 | 35 | ``lookupClass`` works for both Natively implemented classes, as well as 36 | PHP/Hack defined classes. However, it will *not* trigger autoloading of 37 | classes. For that, you need to use ``Unit::getClass( …, true );`` instead. For 38 | example:: 39 | 40 | c_class = Unit::getClass(named_class.get(), true); 41 | 42 | Setting Properties 43 | ------------------ 44 | 45 | Properties can be set with the ``o_set`` method called on a ``ObjectData`` 46 | object. It accepts two required arguments (name, and value) and an optional 47 | third argument (the context). 48 | 49 | The name is passed as a ``String``, and the value as a ``Variant``. As an 50 | example, you can include:: 51 | 52 | obj->o_set(String("nInserted"), Variant(52)); 53 | 54 | This will set the property ``nInserted`` to the (integer) value ``52``. 55 | 56 | The context is argument is required if you want to set the value of a 57 | *private* or *protected* property. The context is ``String`` argument. 58 | 59 | If setting a private property, the context should be the name of the class 60 | that the property is defined on. As an example:: 61 | 62 | const StaticString s_MongoDriverWriteResult_className("MongoDB\\Driver\\WriteResult"); 63 | 64 | obj->o_set( 65 | String("nInserted"), 66 | Variant(52), 67 | s_MongoDriverWriteResult_className.get() 68 | ); 69 | 70 | If setting a protected property, the context should either be the name of the 71 | class on which the property is defined, or an inherited class. 72 | 73 | Getting Properties 74 | ------------------ 75 | 76 | Properties can be read with the ``o_get`` method called on a ``ObjectData`` 77 | object. It accepts one required argument (name), and two optional ones (throw 78 | error, and the context). 79 | 80 | The name is passed as a ``String``, and when you have a public property, you 81 | can read it with:: 82 | 83 | obj->o_get(String("pattern")); 84 | 85 | In order to read a *private* or *protected* property, you need to provide a 86 | context, which is a ``String`` argument. Because this is the third argument, 87 | you also have to provide the second one (which defaults to ``true``). As an 88 | example, to read the ``flags`` private property, you can do:: 89 | 90 | 91 | const StaticString s_MongoDriverBsonRegex_className("MongoDB\\BSON\\Regex"); 92 | const StaticString s_MongoDriverBsonRegex_pattern("pattern"); 93 | 94 | String regex = v.o_get(s_MongoDriverBsonRegex_pattern, false, s_MongoDriverBsonRegex_className); 95 | 96 | Getting a ClassName 97 | ------------------- 98 | 99 | To retrieve the class name of an ``HPHP::Object`` you can call the 100 | ``getClassName`` method. This is a convenience method that actually does 101 | ``.get->getClassName`` — i.e., it goes through ``ObjectData`` first. 102 | 103 | As an example:: 104 | 105 | static Object HHVM_METHOD(MongoDBManager, executeInsert, 106 | const String &ns, const Variant &document, const Object &v) 107 | { 108 | std::cout << v->getClassName().c_str() << " object converted to "; 109 | } 110 | 111 | However, instead of using a class name directly to compare with, it is likely 112 | better to use ``instanceof``. 113 | 114 | Comparing Classes (instanceof) 115 | ------------------------------ 116 | 117 | To check whether an object is of a specific class (or an inherited class), you 118 | can use the ``instanceof`` method on ``HPHP::Object``. A simple equality test 119 | looks like:: 120 | 121 | const StaticString s_MongoDriverBsonRegex_className("MongoDB\\BSON\\Regex"); 122 | 123 | if (v.instanceof(s_MongoDriverBsonRegex_className)) { 124 | } 125 | 126 | This also works for interface implementations. If you consider the ``Regex`` 127 | class to implement ``Type``, then this will also match for the same objects as 128 | in the above example:: 129 | 130 | const StaticString s_MongoDriverBsonType_className("MongoDB\\BSON\\Type"); 131 | 132 | void VariantToBsonConverter::convertPart(Object v) 133 | { 134 | if (v.instanceof(s_MongoDriverBsonType_className)) { 135 | // the class of object v implements "Type" 136 | } 137 | } 138 | 139 | Defining Class Constants 140 | ------------------------ 141 | 142 | You can define class constants directly in the ``ext_*.php`` files, for 143 | example as:: 144 | 145 | class Query { 146 | const FLAG_NONE = 1; 147 | } 148 | 149 | But when the value of the constant is defined in a library that you are 150 | wrapping, you need to do a little bit more work. 151 | 152 | In the ``moduleInit()`` of your extension (in the ``*.cpp`` file), you can use 153 | ``Native::registerClassConstant`` to register these constants. As an example, 154 | you can do:: 155 | 156 | const StaticString s_MongoDriverQuery_className("MongoDB\\Driver\\Query"); 157 | 158 | … 159 | 160 | virtual void moduleInit() { 161 | … 162 | Native::registerClassConstant( 163 | s_MongoDriverQuery_className.get(), 164 | makeStaticString("FLAG_NONE"), 165 | (int64_t) MONGOC_QUERY_NONE 166 | ); 167 | … 168 | 169 | The type that you are registering with is defined in the angle brackets 170 | ``<…>``, in most cases, it's the PHP type with ``KindOf`` in front of it. In 171 | this example, we are registering the class constant 172 | ``MongoDB\Driver\Query::FLAG_NONE`` with the value in ``MONGOC_QUERY_NONE``. 173 | This (C-level) constant is defined in the libmongoc_ library. 174 | 175 | .. _libmongoc: https://github.com/mongodb/mongo-c-driver 176 | 177 | Calling Methods 178 | --------------- 179 | 180 | In order to call a method, you first need to obtain the HHVM equivalent to a 181 | zend_class_entry:: 182 | 183 | Object v; 184 | Class *cls; 185 | 186 | cls = v.get()->getVMClass(); 187 | 188 | On this class object you then run ``lookupMethod`` to obtain a handle to the 189 | function/method:: 190 | 191 | Func *m; 192 | const StaticString s_MongoDriverBsonSerializable_functionName("bsonSerialize"); 193 | 194 | m = cls->lookupMethod(s_MongoDriverBsonSerializable_functionName.get()); 195 | 196 | Arguments are defined in an array of ``TypedValue`` variables:: 197 | 198 | TypedValue args[1] = { 199 | *(Variant(v)).asCell() 200 | }; 201 | 202 | In my example, I convert my ``v`` Object to a Variant:: ``Variant(v)`` and on 203 | this Variant I call ``asCell()`` to create a TypedValue. The pointer to this 204 | TypedValue is then placed in the ``args`` array. 205 | 206 | The obtained method handle can be executed by calling ``invokeFuncFew`` on the 207 | global context ``g_context``. You need to include the ``execution-context.h`` 208 | header for that:: 209 | 210 | #include "hphp/runtime/base/execution-context.h" 211 | 212 | With this global context, you then call the function:: 213 | 214 | Variant result; 215 | 216 | g_context->invokeFuncFew( 217 | result.asTypedValue(), // the by-ref result 218 | m, // the method handle 219 | v.get(), // the object data, providing context 220 | nullptr, // a null pointer (should only be non-NULL) 221 | // when calling __call or __callStatic 222 | 1, args // the number of arguments, and the arguments in 223 | // an array 224 | ); 225 | 226 | 227 | Obtaining Object Properties as Array 228 | ------------------------------------ 229 | 230 | In the simplest form, you can get all the properties of an object as an Array 231 | by just calling ``toArray()`` on the object:: 232 | 233 | Array properties; 234 | Object v; 235 | 236 | properties = result.toArray(); 237 | 238 | However, this also includes private and protected properties. If you do not 239 | want to include those in the resulting array, you need to iterate over the 240 | properties in a specific context. 241 | 242 | The iteration and conversion to Array can be done with the ``o_toIterArray()`` 243 | method on an ``Object``. This method accepts two arguments. The first one is 244 | the context—the class name as a string. The second one a set of options 245 | enumerated by ``IterMode``: ``EraseRefs``, ``CreateRefs`` or ``PreserveRefs``. 246 | 247 | In the following example, we are using ``null_string`` as the class context. 248 | That means that we will never get ``protected`` or ``private`` properties in 249 | the resulting array. We are also just preserving references:: 250 | 251 | Array document; 252 | Object v; 253 | 254 | document = v->o_toIterArray(null_string, ObjectData::PreserveRefs); 255 | 256 | Checking whether a Class is of a Certain Type 257 | --------------------------------------------- 258 | 259 | If you want to find out whether a ``Class`` is a normal class, or something 260 | else, there are a few methods that you can call on a ``Class*`` to find out. 261 | For example, to find out if a class is a "concrete class", and not an 262 | interface, trait, or an enum), you can use:: 263 | 264 | Class *cls; 265 | 266 | isNormalClass(cls); 267 | 268 | There is also ``isTrait()``, ``isEnum()``, ``isInterface()`` and 269 | ``isAbstract()``. 270 | 271 | Please note that ``isNormalClass()`` also allows for abstract classes, so if 272 | you want to check whether a class is a real concrete class, you will need to 273 | check this with:: 274 | 275 | if (isNormalClass(cls) && !isAbstract(cls)) { 276 | --------------------------------------------------------------------------------