Architecture
This document describes the design decisions behind Ariadne. It is a bit dated, but still gives a good overview.
1. Introduction
Ariadne is based around an object store. Meaning, all data in the system are objects. These objects, and their relationship with other objects are stored in a database of objects. Every object knows what data it should have, how to check input for correctness, what operations it provides upon its data, etc.
When a request for an Ariadne object via http comes in, the first thing started is the 'loader' module. This module figures out which object is requested, with what template and arguments. It then Initializes the 'store' module and instructs the store module to call this object with the given template and arguments. The store module retrieves the object from the object store, initializes it and instructs the object to run the given template with the given arguments. This object then starts the template which given the arguments and the object data does it's thing, displays the result and returns control via the object back to the store and finally to the loader. The loader then closes the store and exits.
Inside the template other objects can be called with templates and arguments, via exactly the same calls that the loader used to instantiate this object.
The templates are the only part to generate output.
1.1. Design objectives
Ariadne was developed with a very practical application already in mind. It was developed as the base for a new version of an already existing application. This application allowed people to browse a map of, in this case, the netherlands. The user could select different categories of information and locations of objects corresponding with these categories would be displayed on the map. Each categorie could contain sub-categories and objects of different types. The application was getting unmaintainable, with SQL calls all over the code. The new system needed to abstract from the database, but remain flexible and fast. So the design objectives were:
- Ariadne must implement an object store in which different object types can be placed in different categories.
- This object store must be accessible via a well defined interface. No objects should need access to the store via any other way.
- Ariadne must be able to search a very large set of objects in a part (or all) of the category tree and return the results very fast.
- objects must be able to reside in multiple categories without affecting performance.
- Ariadne should be extensible and scalable.
Furthermore some sort of user management was also needed, as the number of people entering data started to grow:
- Different users should be allowed to access and/or change different sets of data.
These objectives have resulted in some choises which may not be appropriate for all web applications. But if you need a system that will happily search through thousands of objects in a given part of an objecttree and return the results in less than a second, this might be for you.
2. Main Ariadne parts.
2.1. Loader
The first module of Ariadne to run is the loader module. This is a PHP script that interfaces the Ariadne system to the web. The loader initializes the object store module, processes the arguments given via http POST and GET methods, finds the object to call from the path given and calls the store module with these arguments.
The normal way to access an Ariadne object via a webbrowser is no different than accessing a normal web page. The following URL, for example, will show a simple text:
'http://ariadne.muze.nl/muze/loader.php/demo/index.html'
Upon entering the URL:
'http://ariadne.muze.nl/muze/loader.php/demo/classic.edit.phtml'
however, you will be presented with a form in which you can change this text and save it. If you do the URL you'll see next will be something like:
'http://ariadne.muze.nl/muze/loader.php/demo/classic.edit.phtml?name= Demo&text=SomeText&next=Save'
What happens is that Apache (the webserver) finds the loader.php script and runs it. The loader gets the path info of the url (the part after 'loader.php') and splits it into an object location part and a template part. In the first case the object location is '/demo/' and the template is 'index.html'. In the second case the template is 'classic.edit.phtml'. In the last case the template is still 'classic.edit.phtml' but there are arguments too.
The loader takes this location, template and arguments and tries to call the object in the object store corresponding with the given location with the given template and arguments. It doesn't generate any output itself, all output is done by the object itself, specifically the template.
The loader can do more things of course. The standard loader with ariadne has a simple caching mechanism. Before calling the object in the store it first checks to see if there is a page in the cache corresponding exactly with the requested object, template _and_ arguments. If there is, and it has not expired, this page will be shown and no call to the store is made at all.
2.2. Store
2.2.1. Abstract Interface
The store is the core of Ariadne. This module abstracts the actual mechanism used to store objects. Currently there's only a store module for MySQL, but any mechanism may be used. The store is a PHP class with a few functions to manipulate objects and their locations. The store simulates a file system in which the objects are all stored.
Any implementation of the store class should implement at least the following functions as described here. Note though, that the current stores do not implement substores, this will be addressed in later releases of Ariadne.
2.2.1.1. call($template, $args, $objects)
Call takes the list of objects in $objects, instantiates each object therein and calls$object->call($template, $args) in it. The format for $objects is the same as the result format of 'get', 'ls', 'parents' and 'find'. If these functions returned substores, each substore should be called exactly as call was called. This includes the exact call that filled $objects. Example:
original call:
$store->call($template,$args,
$store->get("/asubstore/anobject/"));
substore call:
$object->call($template, $args, $object->get("/anobject/"));
Thus in the $objects variable there must also be the data of the call to 'ls', 'get', 'parents' or 'find'.
The rationale behind this rather cumbersome approach is a bit complex. For the design of the system there was one very important consideration: The system should be able to search entire subtrees of the objecttree very fast. This means that the normal object approach of instantiating each object in the tree, starting at the root, and letting that object call it's children would not do. This resulted in each node containing the full path of that node, and also that of it's parent.
So now when find is called all objects fitting the criteria are found and returned in one go and 'call' simply takes the list of objects found and instantiates and runs them. This approach made it possible to separate the retrieval from the call functionality, so that instead of calling the list of objects, you could also just count them or perhaps do something entirely different yet.
2.2.1.2. get($path)
This function must return the exact object having $path as a location. If no such object exists, it must return the substore, if available, which fits the given path.
The result format must be the same as the input format for $objects in 'call'.
2.2.1.3. ls($path)
This function must return all objects having a location with $path as direct parent. If no such objects are found, it must return the substore, if available, which fits the given path.
The result format must be the same as the input format for $objects in 'call'.
2.2.1.4. parents($path)
Parents must return all objects, in the same store, having a location 'above' $path. If no such objects exist, either the path is invalid, or the calling object is itself the root of the tree. If that object is a substore it should call parents in the store containing it.
The result format must be the same as the input format for $objects in 'call'.
2.2.1.5. find($path, $criteria)
Find must return all objects having a location starting with $path and fullfilling the given criteria.
Criteria correspond to one or more properties. They define the search constraints to which the objects to return should be limited. $criteria has the form:
$criteria["prop_name"]["value_name"][$compare]=$value $compare ::= "<" |">" | "=" | ">= " | "<=" | "LIKE" $value ::= string |number | boolean
A string must start and end with single quote ("'"). All other single quotes must be escaped. String values can contain the wildcard '%' when used in combination with like.
Only objects fullfilling all the criteria are returned. There is no 'OR' operation.
Find also returns all substores having a location starting with $path.
The result format must be the same as the input format for $objects in 'call'.
2.2.1.6. save($path, $type, $data, $properties="", $vtype="")
Save stores the objectdata with the given path and type in the object store. If there already is an object with the given path, only the data, properties and, if set, the vtype are updated.
$vtype is only used for shortcuts. A shortcut must also respond to searches for objecttypes implemented by the object it points to. But when calling the object, it must be isntantiated as a shortcut to function properly. Therefor $vtype is used to search for object types, while $type is used to instantiate it. In all objects, except shortcuts these two are the same.
Properties are special values connected to an object that are used to search for objects. A property consists of a property name and one or more name - value pairs. Values have a type, either 'string', 'number' or 'boolean'. In the case of a string a property value must be enclosed in single quotes. $properties has the form:
$properties["prop_name"]["value_name"][]=$value
Properties are used only by find, as instructed by $criteria.
2.2.1.7. delete($path)
This function deletes the given path from the list of locations. If the object corresponding with this path has no other locations, it and it's properties must be removed also. If there are locations 'under' the given path, delete must not delete the path and fail instead.
2.2.1.8. purge($path)
This function must delete the object pointed to by $path and all other paths pointing to that object. It must then remove all properties set for this object from all property tables.
The function returns the number of paths found and removed or 1 if there was no path found (meaning that the object doesn't exist and therefor purge succeeded while doing nothing.)
2.2.1.9. link($source, $destination)
Link adds a location ($destination) to the object corresponding with $source. This does not imply that all locations 'under' $source also will be present under $destination! Link typically adds a location only for the given object, not for it's children.
This strange behaviour also results from the design objective of a fast searchable system. Find must be able to quickly find all objects under a given path. The only way to do this was to have the complete path of each object as a property of that object. But this means that adding a path to an existing object (creating a link) would mean that either all its children must also add a similar path if links were to display exactly the same behaviour as their original. This is not acceptable for performance reasons.
2.2.1.10. add_property
This function saves a single property entry for the object with id $object. $property is the name of the property to save. It must be a valid property. $values is an associative array describing the values to store in the given property.
The format of $values is:
$values[{name}] = {value}
If a value is a string, it must be enclosed in single quotes (').
2.2.1.11. del_property
This function deletes a single property entry, all property entries for a property or all property entries of all properties for an object, depending on how many of the arguments are filled in.
If only $object is set, it will delete all property entries for all properties for this object.
If $property is set, but not $values, it will delete all property entries for the object with id $object from the given property.
If $values is also set, it will only delete the specific property entry corresponding with those values from the given property.
The format of $values is:
$values[{name}] = {value}
If a value is a string, it must be enclosed in single quotes (').
2.2.1.12. create_property($name, $definition, $indexes) *
* Only available in the 'install' versions of the store class.
This function adds the given property type to the object store. If there already is a property with the given name, it must fail.
The format of $definition is:
$definition["{name}"]["{type}"]={size}
{name} is a string of upto 16 characters
{type} is one of "string","number" or "boolean"
{size} is a number giving the precision of the int or the size
of the string.
The format of $indexes is:
$indexes[]={name} || {name} [ "," {name} ]+
2.2.1.13. remove_property($name) *
* Only available in the 'install' versions of the store class.
This function will remove the property type $name from the object store.
2.2.1.14. add_type($name, $implements) *
* Only available in the 'install' versions of the store class.
This function tells the store that a given type $name, implements another type or interface $implements.
2.2.1.15. del_type($name, $implements) *
* Only available in the 'install' versions of the store class.
This function tells the store that the given type $name, no longer supports the interface or type $implements
2.3. Objects
Objects are imported and instantiated from the store module. The minimal interface of an object to work with the store is:
- An object must have a init method defined as:
'$object->init($store, $path, $data)'
$data is a string containing the object data. This data can be in any form, the standard objects use an object of class 'object'. - An object must have a call method defined as:
'$object->call($template, $args)'
This method may return a value, which the store module will save in an array and return to the calling object or module.
The store interface also sets the variable'$object->parent' directly to the path of the parent of this object. This is purely for performance reasons.
The object's class code must be available in the type_root directory given in the store configuration settings. This file must be named '{type}.phtml', e.g. 'pobject.phtml'.
This leaves a lot of flexibility in the actual implementation of each object. The standard objects implement calls to $template by including the given template in the call function. If this template isn't available in this objecttype's directory, a default template ('default.html') is included instead if available. If this also fails, call must try to find the template in its superclass. But you could for example also call java or com objects, effectively incorporating these applications inside the ariadne application.
2.3.1. Properties
Properties are used to search the object store for objects whose data fullfils a given set of criteria. Only data also stored in a propery can be used as criteria. In effect the system builds a custom index based on the data in the properties.
This results in redundant storage, but also in faster searches and more efficient and flexible storage of the entire object data. As searches are constricted to properties, all other objectdata need not be searchable and can therefore be stored in any form the objecttype defines.
Any property can have multiple name-value pairs. E.g. a property 'location' might have x,y and z values. Any value can be of a specific type; string, number or boolean. This ensures the objectstore can use the most efficient means to store and retrieve the property data.
Any object may set multiple values per property. This results in a limitation in searches, you cannot search for objects _not_ having a certain property value, since objects having multiple values for a property will also be returned, even if one of these does have this property value.
For performance reasons there currently also is no 'OR' operation, all given criteria must be matched by the objects properties. This may be changed in the future though. The simplest solution to this now is to just do an extra search and combine the results.
2.4. Templates
Templates generate all the output of the website. Usually this is where all the application logic resides. Only if a functionality is needed by multiple templates it's usually added to the class definition. A description of all functionality in each class is available in the classes overview.
Templates can contain any valid PHP code and HTML.
The exact environment for a template completely depends on the implementation of each object. Therefore all functionality and system variables available with the standard set of objects will be described in the reference section. The programmer's tutorial gives you an introduction in writing templates.