Search code examples
cphp-extension

PHP Extension: Working with class objects


Let's say I'm creating a PHP 7 extension (in C, NOT C++ and I do not want answers that talk about PHP-CPP at all) and I want to make a php object like Dog and give it variables and functions in the extension itself.

Lets say we have a PHP class like this...

class Dog {
    public name;
    public age;
    private color;

    private function play_ball() {
        echo $this->name . " is playing with a ball!";
    }

    public function get_color() {
        $this->play_ball();
        return $this->color;
    }
}

How would someone do this in an extension written in C? Is this even possible?


Solution

  • I will give you an example of the class, but I'll drop this link to github because I think it'll give you a starting point from where you can get the rest of the information about how to create an extension

    https://github.com/twigphp/Twig/blob/1.x/ext/twig/twig.c

    And here is the class definition code, it's quite verbose and you can use macros and function calls to reduce the amount of boilerplate code, but I'm just trying to explain how to do it, not show you the ultimate, best way to do everything.

    Please Note: I didnt compile this code, although I'm sure it's 99% accurate, maybe you'll have to fix up some small issues, just ask me if you have doubts.

    //  I won't include this file, but just look at the twig.c extension source code
    //  It's mostly boilerplate and you just copy and paste what you need
    #include "php_myanimals.h"
    
    //  This is the "class entry" php will use to define your class
    zend_class_entry *dog_ce;
    
    #define DECLARE_MEMBER(type,name,value,access) \
        type(dog_ce, name, strlen(name), value, access TSRMLS_CC)
    
    #define DECLARE_STRING(name,value,access) \
        DECLARE_MEMBER(zend_declare_property_string,name,value,access)
    
    #define DECLARE_LONG(name,value,access) \
        DECLARE_MEMBER(zend_declare_property_long,name,value,access)
    
    #define SET_PARAM(name,value) \
        zend_update_property(dog_ce, this_ptr, name, strlen(name), value TSRMLS_CC)
    
    #define GET_PARAM(name) \
        zend_read_property(dog_ce, this_ptr, name, strlen(name), 1 TSRMLS_CC)
    
    /* {{{ Method: Dog::__construct() */
    PHP_METHOD(dog, __construct)
    {
        zval *name = NULL;
        zval *colour = NULL;
    
        //  First look in the parameter list for a server string 
        //  The | means that all parameters afterwards, are optional
        if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|z", &name, &colour) == FAILURE)
        {
            EX_INVALID_ARG("MyAnimals\\Dog::__construct(string name, [string colour]), parameters were not valid");
        }
    
        SET_PARAM("name",name);
        SET_PARAM("colour",colour);
    }
    /* }}} */
    
    /* {{{ Method: Dog::playBall) */
    PHP_METHOD(dog, playBall)
    {
        php_printf("%s is playing with a ball!", Z_STRVAL_P(GET_PARAM("name")));
    }
    /* }}} */
    
    /* {{{ Method: bool Dog::getColour() */
    PHP_METHOD(dog, getColour)
    {
        //  yeah, the stupid zend engine programmers made a function
        //  that can take 0, 1 or 2 arguments, but if you want 3 or 4 args?
        //  then you have to use this enormous chunk of boilerplate code
        //  see: https://github.com/twigphp/Twig/blob/1.x/ext/twig/twig.c
        //  search for: TWIG_CALL_USER_FUNC_ARRAY
    
        //  You probably should wrap up this ugly shit in a function call
        //  But I've copied it directly here because it's easier
        zend_call_method(
            &this_ptr, Z_OBJCE_P(this_ptr), NULL, 
            //  CAREFUL! php methods are in lower case!!
            "playball", strlen("playball"),
            // this is zval *retval or NULL if you dont want a return value
            NULL,  
            // means zero parameters
            0, 
            // arg 1
            NULL,
            // arg 2 
            NULL 
            TSRMLS_CC
        );
    
        RETURN(GET_PARAM("colour"));
    }
    /* }}} */
    
    ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 1)
        ZEND_ARG_INFO(0, name)
        ZEND_ARG_INFO(0, colour)
    ZEND_END_ARG_INFO()
    
    const zend_function_entry dog_functions[] = {
        //  public methods
        PHP_ME(dog, __construct, arginfo_construct, ZEND_ACC_PUBLIC)
        PHP_ME(dog, getColour, NULL, ZEND_ACC_PUBLIC)
    
        //  protected methods
        PHP_ME(dog, playBall, arginfo_createmanager, ZEND_ACC_PROTECTED)
    
        PHP_FE_END
    };
    
    /* {{{ PHP_MINIT_FUNCTION */
    PHP_MINIT_FUNCTION(MyAnimals)
    {
        zend_class_entry entry;
    
        //  Creates a class like this \MyAnimals\Dog
        INIT_NS_CLASS_ENTRY(entry, "MyAnimals", "Dog", dog_functions);
    
        dog_ce = zend_register_internal_class(&entry TSRMLS_CC);
    
        //  Declare the state / error properties
        DECLARE_STRING("name", "", ZEND_ACC_PUBLIC);
        DECLARE_LONG("age", 0, ZEND_ACC_PRIVATE);
        DECLARE_STRING("colour", "", ZEND_ACC_PROTECTED);
    
        return SUCCESS;
    }
    /* }}} */