In class-based object oriented programming, classes act as templates for the creation of object instances. They describe the what members each object will have, how these members will be initialized, where they will be visible from. In strictly typed languages it is also possible to define the type of each member. All this information, is essentially meta data for the objects, because it describes the object itself. Therefore, a class contains the meta data of its objects.
Yet, with the use of the few available keywords (public, private, static, abstract etc., we can only describe just a few aspects of an object. It would be nice if we could describe more details. Many languages, including C# and Java, offer this option through the use of compile time attributes on each member of a class. So, putting attributes on a class and its members gives a way to further describe the behaviour of the objects.
In PHP, things are much more limited. First of all, it is not possible to define the type of each field. In addition, there is no standard way to put attributes. Many frameworks provide this by using special tags in documentation comments, but this is not supported directly by the language and therefore it is non-standard and limited. In order to provide this functionality, Oxygen relies on standard PHP features like abstraction, inheritance, and late static binding.
The ability to describe with meta data an object is very useful when it come to the central business objects of an application, which is usually called "The Model". In Oxygen the basic abstract class XItem provides this ability to all the objects that extend it. Every XItem class is linked with an XMeta object that contains all the extra information about the objects. All one has to do is to extend the XItem class and fill the extra information in the XMeta object.
Imagine a simple bookstore application, that has a class Book. To keep things simple, let's say that each book has only one field witht the name "Title". An implementation of this concept in PHP could be like this:
class Book { public $Title; }
That's pretty much everything we can say about the book in the class definition. However there are a lot of extra information that we would like to put here. For example, all books are stored in the table "book" of a database. The title of the book is actually a string, which can be empty but not null. When print in the user interface, the title should go under the label "Title" in English but "Titre" in French. I can continue forever with such requirements. My point is that all this info does not change during the application and describe all book instances. Therefore, we are talking about meta-data of a book which have to be coded in the class of book.
Here is the code to do this in Oxygen:
class Book extends XItem { public $Title; public static function FillMeta(XMeta $m){ $m->SetDBTableName('book'); $m->Title = XMeta::String()->WithLabel('en','Title','fr','Titre'); } }
By overriding the static function FillMeta (with late static binding), we put all the meta-data in an XMeta object. This object is used by Oxygen for many things, including loading from a database, exporting to XML, reading from an HTTP POST request etc.. However, the XMeta object can be used for a variety of other things, so here is an easy way to retrieve it:
$meta = Book::Meta(); echo $meta->Title->GetName() // output: Title echo $meta->Title->GetLabel() // output: Title in English or Titre in French
Each instance of an XItem has a unique identifier under the field id. This identifier is used by Oxygen as a key to the objects. Essentially, if two objects of the same type have the same id, they are considered equal. In addition, there is an id-based central repository of all objects that make it easy to keep just one instance of each object in memory. In other words, if two objects of the same type have the same id, they should be loaded only once.
The id field is also used as the primary key for database operations. For more info about this see Object Relational Mapping.
Most of meta information regard the fields of an object. In a meta object, all fields are represented by instances of the class XMetaField. Every meta field is based on an XType which is responsible for the all the conversions, ie. importing a value into a field of an object and exporting the value from the field (see the chapter The type system). The easiest way to create an XMetaField is from its XType, but for the most common types, this can be also done width convenience functions from XMeta:
$field = MetaString::Field(); // or $field = XMeta::String();
However, the class XMetaField is designed to work in cooperation with the class XMeta. This is because, apart from the type, each field must also have a name. The name is automatically assigned by the XMeta class, after the call to FillMeta, and it is equal to the name of the PHP field inside the final filled meta object.
$field = XMeta::String(); $name = $field->GetName(); // $name is null, because it is not filled in by any XMeta object.
In addition, every field is automatically assigned a label, which is equal to the lemma with the same name in the dictionary (see the chapter on Internationalization). The value of the label can be changed with the method WithLabel which is overloaded.
// No call $m->Title = XMeta::String(); // label: Lemma::Pick('Title') // Call with one string argument $m->Title = XMeta::String->WithLabel('BookTitle'); // label: Lemma::Pick('BookTitle') // Call with one Lemma argument $m->Title = XMeta::String()->WithLabel( Lemma::Pick('BookTitle') ); // label: Lemma::Pick('BookTitle') $m->Title = XMeta::String()->WithLabel( new Lemma('en','Title','fr','Titre') ); // label: Lemma('en','Title','fr','Titre') // Call with two or more string arguments $m->Title = XMeta::String()->WithLabel('en','Title','fr','Titre'); // label: Lemma('en','Title','fr','Titre')
Apart from these general properties, the field carries also specific information regarding the way it is going to be automatically loaded from a database of from an XML document. Here is an example of a field with database and XML mapping:
$m->Title = XMeta::String()->WithLabel('BookTitle')->WithDBAlias('title')->WithXmlAlias('title')->WithXmlBehaviour(Xml::Element);
These extra properties are presented in the chapters Object Relational Mapping and XML mapping respectively.
Apart from the class XItem, Oxygen has also the class XList which can be seen as an array of such items. This array, however, is aware of the extra meta information. This fact will be used by the ORM module to automatically retrieve lists of items for example.
Each list is based on a meta object. For example, a list of books can be created from the book meta object of book:
// either $a = Book::Meta()->MakeList(); // or $a = Book::MakeList();
The list object is used similar to a PHP array:
$a = Book::MakeList(); $a[] = new Book(); echo count($a); // output: 1;
In addition, the XList implements the LINQ interface (see the chapter on LINQ) which means that all LINQ methods are directly available without wrapping with the from() function.
$a = Book::MakeList(); $x = new Book(); $x->Title = 'Lord of the rings'; $a[] = $x; $x = new Book(); $x->Title = 'Alice in wonderland'; $a[] = $x; foreach ($a->Skip(1) as $x) echo $x->Title; // output: Alice in wonderland
It is now evident that there is a direct analogy between the field of an object and the fields of its meta object. the value of a field is a PHP variable while the value of the meta field is an XMetaField object.
$x = new Book(); $x->Title = 'Alice in wonderland'; $m = Book::Meta(); echo gettype($x->Title); // output: string echo gettype($m->Title); // output: object echo get_class($m->Title); // output: XMetaField
When working with the values of a field, we perform operation on the PHP variable. For example, to check whether the title is equal to 'Alice in wonderland', we have to go through an equality operation between the value of the variable and another string, like this:
$compare = $x->Title == 'Alice in wonderland';
This operation compares the value of the specific book with a string. Shifting this operation to the meta object level would imply the comparison of the title any given book with a value. This can be expressed in PHP with a closure:
$compare = function($x){ return $x->Title == 'Alice in wonderland'; };
This type of operation that returns a boolean value is called a predicate. In general, the predicates that operate on a field and a value are very useful. However, it is very common to be hard coded in another language, such as SQL:
SELECT * FROM Book WHERE Title = 'Alice in wonderland' -- a predicate in SQL
One of the main goals of Oxygen is to reduce the development time, which includes not only the time spent on the first version of an application but also the required time to make changes on top of the initial code. This time is directly linked to the modularity and the reusability of the code. In the above example, the query is not at all reusable. the predicate is encoded in SQL and rests inside a PHP string. This gives limited flexibility: there is no direct way to perform operations on predicates. So, if there is a query to find the books published in 2011 and another query to find the books written in english, then there is no way to produce a query to find the english books of 2011. In order to to this, one have to extract the predicates from the initial queries and then perform operations on them. Yet, the predicates would be still strings, and there is no predefined operation to combine the two string predicates. So, the operation is defined ad-hoc: concatenate the two strings with an 'AND' in between.
The other problem with the SQL statement is that it has to be executed in a database server. Once the data is retrieved from the server, the predicate is no longer valid. So, the code is required to check if the title is equal to 'Alice in wonderland' in the database level is different from the code in PHP level and probably the code in Javascript level. Yet, all three pieces do the same thing.
From the above example, it becomes evident that it would be better to work with structures that support all the necessary operations and can be produce the code in any language. For this reason, Oxygen uses the class XPred, which encapsulates an abstract predicate. Much emphasis is given to the predicates based on fields. These predicates can be created directly from the meta fields:
$m = Book::Meta(); $pred = $m->Title->Eq( 'Alice in wonderland' );
Apart from the equality, there are many other predicates based on fields that can be retrieved the same way. These include NotEq (=not equal), IsNull, IsNotNull, Lt (=less than), Le (=less than or equal to), Gt (=greater than), Ge (=greater than or equal to), In (=exists in), NotIn, Like and NotLike.
$m = Book::Meta(); $pred = $m->Year->Ge( 2000 ); // where the year is greater or equal to 2000
It is possible to combine predicates with the boolean operations (AND, OR and NOT) in order to produce other more complex ones.
$m = Book::Meta(); $pred_is_english = $m->Language == 'en'; $pred_is_published_in_2010 = $m->Year->Eq(2010); $pred_is_published_in_2011 = $m->Year->Eq(2011); $pred_is_not_english = $pred_is_english->IsNotTrue(); // NOT $pred_is_english_published_in_2011 = XPred::All( $pred_is_english , $pred_is_published_in_2011 ); // AND $pred_is_published_in_2010_or_2011 = XPred::Any( $pred_is_published_in_2010 , $pred_is_published_in_2011 ); // OR
In addition, it is possible to have predicates with operations on two fields. This is not very often, but it is used in certain cases:
$m = Book::Meta(); $pred_is_a_translation = $m->Language->NotEq( $m->OriginalLanguage );
Predicates can be used directly on an XList, instead of closures. For instance, the following two lists are equivalent:
$list1 = $list->Where( function($x){ return $x->Title == 'Alice in wonderland'; } ); $list2 = $list->Where( Book::Meta()->Title->Eq( 'Alice in wonderland' ) );
However, the first version is limited to be used only in PHP. It does not carry information about how to be converted to SQL or to Javascript code. On the other hand, the second version is linked with the meta object which provides all the information needed for the translation. This will be analysed further in the chapter on Object Relational Mapping.
Much like the predicates, sorting is another operation that is very often coded multiple times in the same application (in SQL, in PHP and in Javascript). Oxygen offers a similar solution with the class XOrderBy. This can also be created from the meta field:
$m = Book::Meta(); $order_by_title_asc = $m->Title->Asc(); $order_by_year_desc = $m->Year->Desc();
Ordering with more than one fields can be done with the method ThenBy
$order_by_year_desc_and_title_asc = $order_by_year_desc->ThenBy( $order_by_title_asc );
The XOrder objects can be used in XList instead of closures. The following two clauses are equivalent, but once again the first one can only be used in PHP, while the second is translatable to other languages.
$list1 = $list->OrderBy( function($x){ return $x->Title; } ); $list2 = $list->OrderBy( Book::Meta()->Title->Asc() );
Finally, it is possible to define a default sorting for items inside the meta class.
class Book extends XItem { ... public static function FillMeta(XMeta $m){ $m->Title = XMeta::String(); $m->OrderBy( $m->Title->Asc() ); } ... }