Work across multiple extensions

PHP scripts can now use this big extension full of classes. But how might other extensions use it?

The size of the binary extension Wcc seems now too big, and contains classes across two namespaces. Hardly everyone is going to want or need the baggage of all of them together. A more useful stragety would be to split them into two or more collections that have minimal external dependencies.

The namespace of a class is part of its class name, and good way to organise classes and their files into a directory tree. There is no problem with sharing use of a namespace between extensions.

Very roughly the Wcc classes cover these functional spaces.

Associative key collections.

Wcc\Config, implements dynamic properties. Wcc\Hmap, implements internal array.

PHP provides two kinds of access, via object properties, (object declared properties with controlled access, dynamic properties) and array. Array is subject to copy on write, making shareable write access only possible by passing as reference.

PHP allows for variable or property names embedded in strings, but not array notation. Arrays of Objects of stdClass can optionally be returned in database query row results.

$s = "Hello $obj->property";
$s = "Hello $obj->{$name}";

PHP provides stdClass for inbuilt dynamic properties.

There is an inbetween, that of the class ArrayObject. This is usable with array notation, but is shareable as an object. A read access performance comparison of all of these will be presented later.

Classes for cache, cache package serialization.

Wcc\ICache, a cache front end for different cache backends. Wcc\ICacheData, a managed cache package handled by ICache.

Support and Cache implementation script classes.

Wcc\CacheAll, create multiple instances of Wcc\ICache from configuration by name, and provide means to read and cache files through a named cache, with attention to expiry. Wcc\Cache\SFile - A useful cache implementation for Session files. prefixes expiry time at beginning of file. Wcc\Cache\Noop - A cache implementation that has always expired. Wcc\Cache\Libmemcached - Uses the Memcached PHP extension Wcc\Cache\Apcu - Uses the APCu PHP extension

Route coding and matching

Wcc\Route, Data for a route - URL, HTML verb, object-method target. Wcc\RouteSet, A serialized collection of Routes, both fixed and regular expression matched. Wcc\RouteMatch, A route, along with its decoded match arguments. Wcc\Target, Route target, a simple class with Object class name, method name, and module name. Wcc\Pair, A simple class to hold two properties. Wcc\RequestGlobals, Interface access to the SuperGlobals for _GET, _POST, _REQUEST, _SERVER. Uses Wcc\Hmap. Wcc\FileUpload, File details for uploads.

Manage view hierarchy and buffering to create HTML output.

This makes use of the Plates-like classes pattern after some shrinkage and mutilation towards simplication.

Wcc\IfLoadHtml, Interface with one function, getHtml(path,data):string Wcc\ViewOutput, Script implementation of IfLoadHtml, called by Plate class to extract data into the symbol table, then file load, amd optionally buffer the output, and return the output.

Wcc\Plate, Manage name and path to template, exposed data, and named sections of html to be inserted. Calls the IfLoadHtml class obtained from Wcc\PlateEngine.

Wcc\PlateEngine, Holds named Wcc\Plates, and shared data, plus other functions that have most likely atrophied. A shared resource for Plate instances. Wcc\SimpleView, Render from a single template file. cd Wcc\HtmlPlates, build and organize a view heirarchy, with data, and start rendering.

Wcc\HtmlGem, A monster html output class, many functions taking array arguments to output defined html segments. Wcc\IfFindLeaf , Interface to find first name-extension match in an ordered list of directories. Wcc\SearchList, Implementation of IfFindLeaf.

Wcc\MoneyFmt , A few functions to format numbers as currency.

Services, Dependency Injection

Wcc\Services, manages two lists, one of active functions/objects/data, another of deferred data, usually callable functions to create something active. If active service isn't found, the deferred list is searched for activation. Services manages a list of singleton classes by class name, "instances". Wcc\ServiceAccess, inherit from this class to use magic method to access an active service, from a services instance, or the global services.

Class finder

Wcc\Finder, breaks down a class name, including namespace, to search for it amoung namespace indexed directory paths.

Class Loader

Wcc\Loader, uses Wcc\Finder, and tries to load the class if found.

ReflectCache

Wcc\ReflectCache Manages a cache of Reflection classes, and is a way of creating a class instance and calling its constructor, with or without arguments.

Assets

Wcc\Assets, Assets are javascript, css, inline styles, injected into HTML outout. Reads a configuation file, and has functions indexed asset selection, and various output generation to call from view templates.

Response generation

Wcc\Headers. Generate headers part of HTML response. Wcc\GlobalResponse. Interact with PHP response generation.

How to break into pieces.

Maybe Singlular piece big enough to stand as an additional extension is the HTML generation classes. Another big piece is the Route related classes, including RequestGlobals. All the rest could be a "core" included with the shared binary.

Make a fresh folder extension start.

Perforce have a decent guide for building a PHP extension. Right now I'm looking at their Section 2, "Generating a PHP Extension Skeleton". This requires having installed a php-src distribution tarball. With latest distribution 8.4.12, have built a debug php version and environment.

In my linux home subdirectory ~/www I type the command.

php php-8.4.12/ext/ext_skel.php --ext runsa --dir .

The skeleton is for C coding. I wanted C++. Minimal changes, make config.m4 work for C++. In config.m4, before the PHP_NEW_EXTENSION macro, add 4 lines, and rename the .c file as .cpp

  FLAGS="-fPIC"
  CXXFLAGS="$CXXFLAGS -Wall -O2 --std=c++23 -I./include"
  PHP_REQUIRE_CXX()
  AC_LANG([C++])

Run phpize, then ./configure, and make. Some warnings come up. Fix them and wrap includes *arginfo.h with an "extern C" wrap and a define guard for good measure. php_runsa.h is the main include, with the extension version define, so move the other includes inside it from runsa.cpp.

#ifndef PHP_RUNSA_H
# define PHP_RUNSA_H

extern "C" {
#include "php.h"
#include "ext/standard/info.h"
#include "runsa_arginfo.h"
};

Oh dear, the script has made a traditional "Hello World" function as test2. Copy the zpp folder to the new project, and recode it using zpp techniques. Include the zpp code. Delete the "For compatibility with older PHP and its define". Function test2 can be rewritten with ZPP classes and a zpp::state_init instance.

// Include all zpp
#include "zpp/base.cpp"
// For debugging with define DEBUG_EXTRA
#include "zpp/show_zpp.cpp"

using namespace zpp;

class Global_init : public state_init {
public:
	str_intern world;

	void init() override {
		world = "World";
	}
};

Global_init Global_;

PHP_FUNCTION(test2)
{   
    zarg_rd args(execute_data);

	str_ptr name;
	str_rc  result;

	args.zstring(name, args.option(1));

	if (!args.throw_errors())
	{
		str_buf buf;

		if (!name.ok())
		{
			name = Global_.world;
		}
		buf << "Hello " << name;
		result = buf.zstr();
	}
	result.move_zv(return_value);
}

There's more! This skeleton needs a module init and shutdown functions, and request shutdown function for the state_init infrastructure.

    STANDARD_MODULE_HEADER,
   "Runsa",					    /* Extension name */
	ext_functions,		        /* zend_function_entry */
    PHP_MINIT(runsa),		    /* PHP_MINIT - Module initialization */
	PHP_MSHUTDOWN(runsa),	    /* PHP_MSHUTDOWN - Module shutdown */
	PHP_RINIT(runsa),			/* PHP_RINIT - Request initialization */
	PHP_RSHUTDOWN(runsa),		/* PHP_RSHUTDOWN - Request shutdown */

And all the zpp::state_init static instances need to be told what to do.

PHP_MINIT_FUNCTION(runsa)
{
#ifdef DEBUG_EXTRA
	dump_info::run_state_ = true;
#endif
    // Initialize static data instances
	zpp::state_init::init_all();
}

PHP_MSHUTDOWN_FUNCTION(wcc)
{
	zpp::state_init::end_all();

#ifdef DEBUG_EXTRA
	dump_info::run_state_ = false;
#endif
	return SUCCESS;
}

/* {{{ PHP_RINIT_FUNCTION */
PHP_RINIT_FUNCTION(runsa)
{
#if defined(ZTS) && defined(COMPILE_DL_WCC)
	ZEND_TSRMLS_CACHE_UPDATE();
#endif
	zpp::state_init::init_request();
	return SUCCESS;
}
/* }}} */

PHP_RSHUTDOWN_FUNCTION(runsa)
{
	zpp::state_init::end_request();

#ifdef BASE_DEBUG
	zpp::mgr_link::report();
#endif
	return SUCCESS;
}

After all this need to do "make install", edit the php.ini for the test PHP environment for loading this extension. Run "php -m" to find the name of the new module in the module list.

Write and run a little test script. Oh wait, they are exist already in the tests folder. Run "php run-tests.php". Get result of "Tests passed : 3 (100.0%) (100.0%)".

The next stage is to remove the baby test functions, and put in the intended class and methods.