-
Notifications
You must be signed in to change notification settings - Fork 0
Internal API
The aim of this document is to explain the functions used internally in the development of the C extension.
The way the code is written is not the usual, if you compare the code of Phalcon with other extensions you'll see a huge difference, this has several objectives:
- Don't repeat yourself, if PHP has a functionality already developed, why do not simply use it?
- Writing the code more in a PHP style, this will help to PHP developers to introspect easily understanding the internals of PHP objects. Reflection
- Help the Phalcon user to obtain more human backtraces
- Avoid dealing with low-level issues, whenever possible.
We, the creators of Phalcon, are mainly PHP programmers, we are lazy and do not want to deal 100% of the time with low-level details and segmentation faults. Moreover we want a fast and stable framework. For this reason we have created the Phalcon API. The use of this API helps us to write C code in a PHP style. We have developed a number of functions to help the programmer to write code more interoperable with PHP in a easier way.
Phalcon API is based on the Zend API, but we have added more features to facilitate the work. Phalcon is a very large project, frameworks need to be developed and improved every day, the Phalcon API helps us write code in C more stable and familiar to PHP developers.
Zvals: Most variables used in the framework are Zvals. Each value within a PHP application is a zval. The Zvals are polymorphic structures, ie a zval can have any value (string, long, double, array, etc.). Inspecting some code you will see that most variables are declared Zvals. PHP has scalar data types (string, null, bool, long and double) and non-scalar data types (arrays and objects). There is a way to assign a value the zval according to the data type:
PHALCON_INIT_VAR(name);
ZVAL_STRING(name, "Sonny", 1);
PHALCON_INIT_VAR(number);
ZVAL_LONG(number, 12000);
PHALCON_INIT_VAR(price);
ZVAL_DOUBLE(price, 15.50);
PHALCON_INIT_VAR(nothing);
ZVAL_NULL(nothing);
PHALCON_INIT_VAR(is_alive);
ZVAL_BOOL(is_alive, false);
Zend Class Entries: Another common structure we used is the zend class entry. That structure help us to describe a class, its name, method, default properties, etc.
//Get the class entry
class_entry = Z_OBJCE_P(this_ptr);
//Print the class name
fprintf(stdout, "%s", class_entry->name);
As you may know, the memory management in C is all manual. Within a PHP extension, we can leverage the use of the Zend Memory Manager, however management remains manual.
To help in the creation of Phalcon, we have created a memory manager that basically track every variable allocated in order to free it before exit the active method:
For example:
PHP_METHOD(Phalcon_Some_Component, sayHello){
zval *greeting = NULL;
PHALCON_MM_GROW();
PHALCON_INIT_VAR(gretting);
ZVAL_STRING(greeting, "Hello!", 1);
PHALCON_MM_RESTORE();
}
PHALCON_MM_GROW starts a memory frame in the memory manager, then PHALCON_INIT_VAR allocates memory for the variable greeting, before exit the method we call PHALCON_MM_RESTORE, this releases all the memory allocated from the last call to PHALCON_MM_GROW.
By this way, we can be sure that all the memory allocated will be freed, avoiding memory leaks. Let's pretend that we used 10-15 variables, releasing manually the memory of them can be tedious.
As noted above, all variables must be initialized before use. Even, it should be reset again when we change its value, for example:
//Declare the variable
zval some_number = NULL;
//Initialize the variable and assign it a string value
PHALCON_INIT_VAR(some_number);
ZVAL_STRING(some_number, "one hundred");
//Reinitialize the variable and change its value to long
PHALCON_INIT_VAR(some_number);
ZVAL_LONG(some_number, 100);
When declaring the variables is important to initialize them to NULL. By doing this, PHALCON_INIT_VAR will know if the variable needs memory or it already have memory allocated.
PHP is a dynamic language, we can do almost any operation between two variables, regardless of type. Sometimes we do not know exactly the type of data that have the variables, using the Zend API we can do operations between them seamlessly:
//First variable is string but it's a numeric string
PHALCON_INIT_VAR(first_var);
ZVAL_STRING(first_var, "100.10", 1);
//Second variable is long
PHALCON_INIT_VAR(second_var);
ZVAL_LONG(second_var, 150);
//add_function will make the necessary conversions to produce the addition
PHALCON_INIT_VAR(result);
add_function(result, first_var, second_var);
Concatenation is one of the most common operations we do in PHP. However using the Zend API can be tedious when concatenate many values, for example:
// The following concatention using just Zend API:
//
// $month = "July"; $day = 1;
// $today = "Today is ".$month." ".$day;
PHALCON_INIT_VAR(month);
ZVAL_STRING(month, "2012", 1);
PHALCON_INIT_VAR(day);
ZVAL_LONG(day, 1);
PHALCON_INIT_VAR(today_is);
ZVAL_STRING(today_is, "Today is", 1);
PHALCON_INIT_VAR(first_part);
concat_function(first_part, today_is, month);
PHALCON_INIT_VAR(space);
ZVAL_STRING(space, " ", 1);
PHALCON_INIT_VAR(second_part);
concat_function(second_part, space, day);
PHALCON_INIT_VAR(today);
concat_function(today, first_part, second_part);
Another way to do that is use sprintf, in this case, you need to be completely sure that the variables have all string types:
char *final_string;
zval *final;
PHALCON_INIT_VAR(month);
ZVAL_STRING(month, "2012", 1);
PHALCON_INIT_VAR(day);
ZVAL_STRING(day, "1", 1);
final_string = emalloc(sizeof(char)*(Z_STRLEN_P(month)+Z_STRLEN_P(day)+12)));
sprintf(final_string, "Today is %s %s", Z_STRVAL_P(month), Z_STRVAL_P(day));
PHALCON_INIT_VAR(final);
ZVAL_STRING(final, final_string, 0);
It's short, but if some of your variables aren't string you will get a segmentation fault or an unexpected behavior.
To help to solve this problem, we have created a set of macros to concatenate zvals and strings:
PHALCON_INIT_VAR(month);
ZVAL_STRING(month, "2012", 1);
PHALCON_INIT_VAR(day);
ZVAL_STRING(day, "1", 1);
PHALCON_INIT_VAR(today);
PHALCON_CONCAT_SVSV(today, "Today is", month, " ", day);
Other examples:
PHALCON_CONCAT_VV(result, month, day); //July1
PHALCON_CONCAT_VSV(result, month, ", ", day); //July, 1
PHALCON_CONCAT_SVSV(result, "Today is", month, " ", day); //July 1
PHALCON_CONCAT_SVSVSV(result, "Today is", month, " ", day, ", ", year); //July 1, 2012
S=String and V=Zval, just put the S and V to get the right concatenation macro. Easy, no?
Although the Zend API, and provides various functions for working with arrays, with Phalcon API we added other. Specifically helping to maintain the reference counting correctly:
//Declare the variable
zval fruits = NULL;
PHALCON_INIT_VAR(fruits);
array_init(fruits);
//Adding items to the array
add_next_index_stringl(fruits, SL("apple"), 1);
add_next_index_stringl(fruits, SL("orange"), 1);
add_next_index_stringl(fruits, SL("lemon"), 1);
add_next_index_stringl(fruits, SL("banana"), 1);
//Get the first item in the array $fruits[0]
PHALCON_INIT_VAR(first_item);
phalcon_array_fetch_long(&first_item, fruits, 0, PH_NOISY_CC);
Mixing both string and number indexes:
//Let's create the following array using the Phalcon API
//$fruits = array(1, null, false, "some string", 15.20, "my-index" => "another string");
PHALCON_INIT_VAR(fruits);
array_init(fruits);
add_next_index_long(fruits, 1);
add_next_index_null(fruits);
add_next_index_bool(fruits, 0);
add_next_index_stringl(fruits, SL("some string"), 1);
add_next_index_double(fruits, 15.2);
add_assoc_stringl_ex(fruits, SL("my-index")+1, SL("another string"), 1);
//Updating an existing index $fruits[2] = "other value";
phalcon_array_update_long_string(&fruits, 2, SL("other value"), PH_SEPARATE TSRMLS_CC);
//Removing an existing index unset($fruits[1]);
phalcon_array_unset_long(fruits, 1);
//Removing an existing index unset($fruits["my-index"]);
phalcon_array_unset_string(fruits, SL("my-index")+1);
Calling functions is another common action we do when create programs in PHP. Although many functions may be called directly using its internal function pointer in C, others can simply being called using the PHP userland. For example:
//$length = strlen(some_variable);
PHALCON_INIT_VAR(length);
PHALCON_CALL_FUNC_PARAMS_1(length, "strlen", some_variable);
The macro PHALCON_CALL_FUNC_PARAMS_1 calls functions that requires 1 parameter returning a value. There are another macros to call functions in the PHP userland:
//Calling substr() with its 3 arguments returning the substring into part
PHALCON_INIT_VAR(part);
PHALCON_CALL_FUNC_PARAMS_3(part, "substr", some_variable, start, length);
//Calling ob_start(), this function does not return anything
PHALCON_CALL_FUNC_NORETURN("ob_start");
//Closing a file with fclose
PHALCON_CALL_FUNC_PARAMS_1_NORETURN("fclose", file_handler);
As mentioned above, PHP already has many things going, the fact that an extension in C, does not mean we going to reinvent the wheel all over again develop. Also worth saying that we also try to avoid using very low-level features of C, if PHP already has its own version. PHP functions help us get a behavior similar to that programming in the PHP would. In this way we avoid possible errors and as result the framework works as if it were PHP.
The following code opens a file in C and write something on it. Its functionality is limited because it only works on local files:
FILE * pFile;
pFile = fopen ("myfile.txt","w");
if (pFile != NULL) {
fputs ("fopen example",pFile);
fclose (pFile);
}
Now write the same code using the PHP userland:
PHALCON_INIT_VAR(mode);
ZVAL_STRING(mode, "w", 1);
PHALCON_INIT_VAR(file_handler);
PHALCON_CALL_FUNC_PARAMS_2(file_handler, "fopen", file_path, mode);
if (PHALCON_IS_NOT_FALSE(file_handler)) {
PHALCON_INIT_VAR(text);
ZVAL_STRING(text, "fopen example", 1);
PHALCON_CALL_FUNC_PARAMS_2_NORETURN("fputs", file_handler, text);
PHALCON_CALL_FUNC_PARAMS_1_NORETURN("fclose", file_handler);
}
Although both codes perform the same task, the former is more powerful as it could open a PHP stream, a file in a URL or a local file.We can also write the same code using the PHP API, without losing functionality. However the above code is more familiar if we are primarily PHP developers.
The same code in PHP:
$fp = fopen($file_path, "w");
if($fp){
fputs($fp, "fopen example");
fclose($fp);
}
Instantiate objects of the framework classes is easy:
PHALCON_INIT_VAR(route);
object_init_ex(route, phalcon_mvc_router_route_ce);
PHALCON_INIT_VAR(pattern);
ZVAL_STRING(pattern, "#^/([a-zA-Z0-9\\_]+)[/]{0,1}$#", 1);
PHALCON_CALL_METHOD_PARAMS_1_NORETURN(route, "__construct", pattern, PH_CHECK);
The above code is the same as doing in PHP:
$route = new Phalcon\Mvc\Router\Route("#^/([a-zA-Z0-9\\_]+)[/]{0,1}$#");
Moreover, if the class is not Phalcon objects must then initialized as follows:
zend_class_entry *reflection_ce;
//Obtain the ReflectionClass class entry, this will also call autoloaders
reflection_ce = zend_fetch_class(SL("ReflectionClass"), ZEND_FETCH_CLASS_AUTO TSRMLS_CC);
//Instantiate the Reflection object
PHALCON_INIT_VAR(reflection);
object_init_ex(reflection, reflection_ce);
//Pass a class name as constructor's parameter
PHALCON_CALL_METHOD_PARAMS_1_NORETURN(reflection, "__construct", class_name, PH_CHECK);
When you throw an exception using the Phalcon API, the current flow of execution will be stopped, returning to the last PHP code block when a Phalcon method where called.
There are two ways to throw exceptions, the first, when the exception object only receives a string as parameter:
PHALCON_THROW_EXCEPTION_STR(phalcon_exception_ce, "Hey this is an exception");
Or building the exception manually and then throwing it:
PHALCON_INIT_VAR(exception_message);
PHALCON_CONCAT_SVS(exception_message, "Unable to insert into ", table, " without data");
PHALCON_INIT_VAR(exception);
object_init_ex(exception, phalcon_db_exception_ce);
//The exception constructor must be manually called
PHALCON_CALL_METHOD_PARAMS_1_NORETURN(exception, "__construct", exception_message, PH_CHECK);
phalcon_throw_exception(exception TSRMLS_CC);
return;