The type system

The type system is the central component of Oxygen.

A great deal of work in web development is the conversion of data from one type to another. These conversions usually include parsing or escaping, both of which are a source of bugs. The role of the type system is to streamline the conversions so that the final code is easier to produce and less error prone.

Let's have a simple example. A web application has a form to rename an item. This form contains a simple text box and a save button. Upon clicking save, the contents of the field are validated and saved in the database. This simple example, which is typical in web applications, involves a great deal of conversions:

|------------| |------------| |-----------------| |----------------| |--------------------| | | -(PDO)-> | PHP String | -(convert)-> | Object Property | -(escape)-> | HTML attribute | -(browser)-> | | | | |------------| |-----------------| |----------------| | | | DB VARCHAR | | HTTP POST argument | | | |-----------------| |-----------------| |---------------| | | | | <-(PDO)- | Bound Parameter | <-(bind)- | Object Property | <-(convert)- | $_POST string | <-(PHP)--- | | |------------| |-----------------| |-----------------| |---------------| |--------------------|

During this round-trip the name is converted 8 times.

4 of the conversions are done by the existing infrastucture (the database, the database driver, PHP, Apache and the final browser). However, the other 4 are left to the programmer, and, arguably, this is a great source of bugs if not done in a systematic way.

Those conversions depend on the type of the variable. A string is parsed, assigned, converted and escaped in a different way than a boolean or a date. However, no matter how the variable is escaped or represented in all these places, one could argue that the overall abstract type does not change. A string is alweays a string no matter if it is stored as a VARCHAR in the database or as an attribute in HTML. An integer is always an integer, even if it is represented as a string inside the $_POST array.

Oxygen introduces the idea of XType which represents the abstract Type of a variable without taking into account where it is stored. The XType of a variable knows how to convert the variable from one type to another.

Primitive XTypes

The following data types are supported by Oxygen.

XType PHP Type Default value SQL Type PHP literal SQL literal (MySQL) URL value (french) HTML value (french)
MetaString string '' VARCHAR 'It\'s me & you' 'It\'s me & you' It's%20me%20%26%20you It"s me &amp; you
MetaInteger integer 0 INT 3 3 3 3
MetaDecimal float 0.0 DECIMAL 3.14 3.14 3,14 3,14
MetaBoolean boolean false TINYINT true 1 true true
MetaNull null null * null NULL
MetaID ID new ID(0) INT new ID(3) 3 00000003 00000003
MetaLemma Lemma new Lemma() VARCHAR new Lemma('en','Title','fr','Titre') 'en~Title~fr~Titre' en~Title~fr~Titre en~Title~fr~Titre
MetaDateTime XDateTime new XDateTime() DATETIME XDateTime::Make(2010,10,21,12,30,15) '2010-10-21 12:30:15' 20101021123015 20101021123015
MetaDate XDate new XDate() DATETIME XDate::Make(2010,10,21) '2010-10-21 00:00:00' 20101021000000 20101021000000
MetaTime XTime new XTime() DATETIME XDate::Make(12,30,15) '2000-01-01 12:30:15' 20000101123015 20000101123015
MetaTimeSpan XTimeSpan new XTimeSpan() INT XTimeSpan::Make(1,30,20,324) 5420324 5420324 5420324

In addition, all of the above types have a nullable equivalent (except MetaNull of course). The nullable type has the same name as the not nullable with the suffix -OrNull. So, the nullable MetaString is the MetaStringOrNull and the nullable MetaBoolean is the MetaBooleanOrNull. When using non-nullable data types, a null value automatically gets coverted to the default value. So, when an MetaBoolean encounters a null value, it will convert it to false. On the contrary, the MetaBooleanOrNull will return a null.

The actual conversions are done by a set of functions that each XType has. Those functions are separated into two large groups, the exporters and the importers. The exporters convert a PHP value to an external form (such as an SQL literal), while the importers convert an external value (such as a database value) back into a PHP variable.

$sql_literal = MetaString::ExportSqlLiteral( 'This is a string', Database::MYSQL ); $php_integer = MetaInteger::ImportHttpValue( '1' );

Usually, the XType to be used can be inferred from the type of the PHP variable. The function XType::Of returns the default XType of a variable. This comes with a little runtime overhead, but it is very convenient.

$s = 'This is a string'; echo XType::Of($s)->ExportSqlLiteral( $s, Database::MYSQL ); // output: 'This is a string' $i = 5; echo XType::Of($i)->ExportSqlLiteral( $i, Database::MYSQL ); // output: 5

As these functions are used very frequently, all exporters come with some helping wrapper classes, that will infer the XType and execute the exporting function in a lazy fashion. In addition, they will use the current open database connection to find the database type.

echo new Sql ('It\'s me & you'); // output: 'It\'s me & you' echo new Html('It\'s me & you'); // output: It's me &amp; you echo new Js ('It\'s me & you'); // output: 'It\'s me & you' echo new Url ('It\'s me & you'); // output: It's%20me%20%26%20you echo new Xml ('It\'s me & you'); // output: It's me &amp; you

In contrast, when importing a value, the type is not known unless told as PHP does not keep the type of uninitialised variables. As a result, importing a variable requres always to use the correct type.

Import sources

When exporting, the source is always some PHP variable. On the other hand, when importing the source has to be defined. Oxygen offers some standard sources to make things easier. These sources wrap the values to be imported in a structure that makes the conversion easier.

One of the sources is the DBReader class (see the chapter Database Abstraction Layer). Each field of the current record can be imported with the help of an XType.

$dr = Database::Execute('SELECT * FROM Book'); while($dr->Read()) { $id = $dr['id']->CastTo( MetaID::Type() ); $NumberOfPages = $dr['NumberOfPages']->CastTo( MetaInteger::Type() ); $Author = $dr['Author']->CastTo( MetaString::Type() ) } $dr->Close();

Another import source is the Http class with the arrays Http::$POST, Http::$GET and Http::$ANY.

$id = Http::$GET['id']->CastTo( MetaID::Type() ); $username = Http::$POST['username']->CastTo( MetaString::Type() );

There are also some shortcut methods for the basic types, that can be used instead of CastTo.

$offset = Http::$GET['offset']->AsInteger();