SQL and Table Model Classes - Wcd namespace

The Wcd namespace is for Database Management. It presents several classes that evolved for handling SQL dialects, database table model classes. The aim is to abstract away details of SQL databases. It has a mix of approaches, seen in some other PHP framework classes. It presented challenges for personal learning about design of such objects that need to usefully work together, and resulted in some C++ additions to the PHP-ZPP framework basics.

Avoid use of C++ std::exception, how to return error strings along with values

PHP uses exception handling, with try - catch and exception class matching. Error conditions that arise in C++ code need to create a PHP exception class. There are a number of zend API methods to call which will "post" an exception, but using them to create the exception information, does not break the flow of code execution. They do not "throw", the way C++ exceptions do , by unwinding the execution stack back to a previously set place in the code. It relies on the Zend interpreter to check for a posted exception after returning from a called object method or function, to do the cleanup work, and interpreter stack unwinding work.

Throwing a C++ style exception that is not caught before returning from the Zend call, will exit script execution. To have controlled C++ exceptions would mean wrapping all suspect exception throwing function in a try - catch, and then using the captured information to post a Zend exception.

Initially the Wcd C++ classes had some ackward C++ exception throws, which were eventually rooted out, and replaced with return value structure that also contains an optional pointer to an error buffer.

The "bireturn" type

The root class is struct error_return, defined in zpp/bireturn.h.

namespace zpp {

struct error_return {
	str_buf*  errors_;

	error_return() : errors_(nullptr) {}
    bool has_errors() const { return (errors_); }
    bool     throw_errors(const char* fncstr = nullptr);
    str_buf& error();
    //...
};

template <typename T>
struct bireturn  : public error_return 
{
	T   	  value_;

	bireturn() : error_return()
	{
	}
};

//...
bool 
error_return::throw_errors(const char* fncstr)
{
	if (errors_)
	{
		*errors_ << "\n*** ERROR " << fncstr;
		str_rc s = errors_->zstr();

		zend_throw_error(zend_ce_error,"%s", s.data());
		delete errors_;
		errors_ = nullptr;
		return true;
	}
	return false;
}

There is more, but the idea is all functions that call a function that returns some form of error_return, must check if it "has_errors()". It must either stop the propagation, or must move the errors into its own form of error_return. The Zend function or method, must be the final check on any type bireturn, and always check "has_errors" or call throw_errors(), which will setup a final Zend exception posting with a message, if errors_ is not a nullptr.

This is of course more coding work than throwing and catching exceptions. The execution inefficiency of returning a more complex type can be offset by simple coding style to allow "return value copy ellision", where one return value is declared inside the function body, and C++ pushes a pointer to the recieving value, for return value optimization.

This mechanism could be made more intricate. For instance a stack history representation could be linked. This seems fine as way of removing the need for C++ exception handling. The errors are mainly used to indicate development - coding errors, for strategic correction.

WeakReference - zpp::weak_ref

PHP has a memory garbage collection to clean up circular reference object cycles. This can be helped by using a "WeakReference" class. This enables other classes to hold a reference to an object, without being counted in the objects reference count.

This was deemed useful for the Wcd\IDriver class. Most classes can hold a WeakReference to it. When the actual IDriver class object is required, a temporary object variable is obtained by a call to the WeakReference "get()",

A garbage collection problem in the class relations for Wcd, is that the IDriver class manages an array of instances of database model classes, and each of this keeps a "Builder" class that keeps a reference to IDriver, which makes a full circle of object references. PHP garbage collection is said to be able to detect such cycles, but this will be aanan execution cost. To avoid undoing the effort creating the origin design, it seemed easier to make most stored references to IDriver to be a WeakReference.

IDriver carries its own WeakReference instance, without making another cycle. Weakreference is also a normal reference counted PHP class object, so on WeakReference object can be shared around for every IDriver instance.

WeakReference became useful, especially for enabling good memory cleanup when using a debug compiled version of PHP. A C++ wrapper "zpp::weak_ref" is derived from "zpp::obj_rc", and zpp::zarg_rd has a "weakref" method to initialize it from Zend function arguments held in "zend_execute_data".

This makes it worthwhile to use a "fn_call" object to cache the call information for "weakref::create", and also the get() method of a WeakReference object. The final result is two access functions.

// In zpp/fn_call.h
    obj_rc weakref_create(obj_ptr obj);
    obj_rc weakref_get(obj_ptr wref);

Testing weakref_get, made a discovery that the set_obj method of fn_call needed to update the zend_object* property object of not only the zend_fcall_info, but also the zend_fcall_info_cache, otherwise the cached object property from the first call would continue to be used.

// In zpp/fn_call.cpp
void 
fn_call::set_obj(zend_object* obj)
{
    //TODO: ?Why requires object to be set in both structures.
    //On first call the cache_ object value is set by PHP.
    
    fci_.object = obj;
    cache_.object = obj;
}