Debug binaries are big

The simple Runsa module is reported as 1011632 bytes. This seems very big. The everything Wcc extension with umpteen classes as a debug compile is 7512928 bytes. Only seven times as big.

Optimized binaries are not too big?

Compiled for the systems PHP environment, which is non-debug, the runsa.so binary is 237904 bytes. Most of this will be from the zpp code, seemingly used here or not. The everything Wcc is 1823376 bytes, and about the same ratio. One can expect debug binaries to be more than 4 times the size of optimized binary builds.

The zpp class methods divide up a lot of callable reusable inlined PHP manipulation code between them.

Make the class stubs

This is "almost" cut and paste from the class to be hardcoded. Only the member headers and methods declarations without method code. The _arginfo.h generate script is very good, but has some restrictions. The namespace declaration is used, but it will not process "use" statements as yet, to indicate what the full namespace path names are of any "foreign" class name, so class names from other namespaces need to be written in full for each usage. This alone suggests using the most common namespace prefix and declaring it as the namespace for the stub class, as much as possible to have relating classes in the same namespace. It is possible to make mistakes when making and altering complex class name trees.

Only public or protected methods need to be exposed.

In the stub file of a class, only public or protected methods or static functions need to be declared. Luckily this Run class has only two of these altogether, and one is the __constructor! The main concern of __constructor is to set initialize all the read-only properties. The execute function, is manage the initialization, processing and destruction cleanup cycle, and as a top function, calls everything else, and is not itself called, except for providing values from named fixed properties. The stub arginfo.h generate C/C++ code which creates all of these declared properties in the class registration function. The __constructor is needed to get an instance variable to fetch its property parameters. A __destructor function is added for any needed run-time cleanup.

Inside these two declared functions all methods of implementation are hidden.

Write a build script

It gets boring to always type out make and arguments, so a build.sh script can dictate cleanups, rebuild and install command orders. To tidy a bit, create a stubs folder and move the stub.php and arginfo.h into it. Change the include path in runsa.cpp

#!/bin/bash
# buildme script
make clean
make stubs/runsa_arginfo.h
make 

PHP has only one table for global functions

Once the function declarations test1() and test(2) are deleted, and replaced with the Run class declaration the "ext_functions" array declaration disappears from arginfo.h as well, and can be replaced with a nullptr, in the zend_module_entry declaration. Please note that the instruction comments "@generate-class-entries" have to be at the beginning, before the namespace declaration, not after, or else only a very tiny amount of declaration code will be generated, without class entries or registration function.

<?php
/**
 * @generate-class-entries
 * @generate-legacy-arginfo 80400
 * @undocumentable
 */
namespace Wcc;

class Run {
    public readonly string $phproot;
    public readonly Finder $finder;
    //... more property declarations

    public function __constructor(string $phproot) {}
    public function __destructor() {}

    public function execute(string $bootstrap) : void {}
};

Create run.h and run.cpp

This class is going to be in namespace Wcc, for both PHP and C++, so make a folder wcc. Define guard their contents, in old-fashioned style. The extension main file will include the run.cpp, and test if defined for its inclusion. Its declaration is in run.h. Runsa is also the name of the main extension file, so change it to php_runsa.cpp, and update the config.m4 with this. Now this better matches the php_runsa.h. Will have to run phpize and ./configure again.

Explain Runsa

This class is going to have to be responsible for loading some other PHP files, but already there is no extension class for this. A simple solution is to create a simple PHP script custom loader function that calls require_once. If the name is agreed to be "simple_loader" for instance, there is no need to pass its name, so it can be hard-coded.

// index.php
// Required by run class, to bootstrap a more complex auto loader class
function simple_loader(string $file) : mixed 
{
    return require_once($file);
}

$run = new Run("wc/php");

$run->execute("bootstrap.php");

C++ class member declarations line up along the PHP api.

//wcc/run.h
namespace wcc {
	using namespace zpp;

	class Run : public base_d {
	public:

		static base_obj_mgr<Run> omg;

		void construct(str_ptr php_root);
		void destruct();
		void execute(str_ptr bootstrap);
	};
};

When the run_arginfo.h generated it has class method declarations and class register function, that can be copy-pasted, and fleshed out in wcc/run.cpp. The destruct() is easy, their are no parameters, and the

//These are outside the namespace brackets
using namespace zpp;
using namespace wcc;

ZEND_METHOD(Wcc_Run, __construct)
{
	zargs_rd args(execute_data);
	str_ptr path;

	args.zstring(path, args.need(1));

	if (!args.throw_errors())
	{
		Run* cobj = zobj_toc<Run>(ZEND_THIS);
		cobj->construct(path);
	}
}

ZEND_METHOD(Wcc_Run, __destruct)
{
	ZEND_PARSE_PARAMETERS_NONE();
	Run* cobj = zobj_toc<Run>(ZEND_THIS);
	cobj->destruct();
}

ZEND_METHOD(Wcc_Run, execute)
{
	zargs_rd args(execute_data);
	str_ptr bootstrap;

	args.zstring(bootstrap, args.need(1));

	if (!args.throw_errors())
	{
		Run* cobj = zobj_toc<Run>(ZEND_THIS);
		cobj->execute(bootstrap);
	}
}

PHP_MINIT_FUNCTION(wcc_run_reg)
{
	Run::omg.classEntry(register_class_Wcc_Run());
	return SUCCESS;
}

Error exceptions

The zargs_rd code zstring(str_ptr& s, zval* ) recieves the indexed zval* from the parameters array in the execute_data structure. If it doesn't exist or isn't a string, an error flag and message buffer is created, and these will be posted back to the zend engine with zend_throw_error. A C++ or C exception isn't actually thrown, as an error check and real PHP error exception are thrown by the ZEND engine after the function exits.

The module initialization stage may throw C++ std::logic_error, especially from the base_obj_mgr code, for things like nullptr, and on this the PHP instance will giveup with an error.

So far the zpp only posts, not throws zend_error exceptions. This is something that may later need to be revised, as these have so far just used the zend_ce_error class, not the zend_ce_exception class. The author is against the idea of making lots of different error exceptions for every class, since PHP usually provides excellent location information, except for where it came from in the C++ code, which will be hinted by the message content.

To have PHP produce its versions of argument error messages, uses the ZEND_PARSE_PARAMETERS_START and friends macros.

Need the PHP_MINIT_FUNCTION to register class

This needs to be called from the main module code, during module initialization, or the Run class will not be available. If their are multiple class includes, the #ifdef make it easier to not include them.

The Run::omg.classEntry function needs to be called with zend_class_entry pointer result. This is an opportunity for derived manager classes to tweak their structure of zend_object function handlers, or do other sophisticated things during module initialization.

Make sure that Run::omg has declared its instance of course, otherwise segfault.

// inside the PHP_MINIT_FUNCTION(runsa) function
#ifdef WCC_RUN_CPP
	PHP_MINIT(wcc_run_reg)(INIT_FUNC_ARGS_PASSTHRU);
#endif

Do something useful in the C++ class

Now this chapter has gone for long enough, its time to start another with whatever surprises will be encountered while doing the yet to be coded C++ methods for the Run class.