1. Introduction
Ariadne is primarily a web application framework. One of the most obvious applications for it is as a CMS (Content Management System). But the architecture of Ariadne is designed to be much more flexible than that.
Ariadne's design requirements were:
- Ariadne must be able to store different types of information in varying categories.
- The storage layer must have a well defined and strictly enforced API (interface).
- Ariadne must be able to search through very large sets of data in a part (or all) of the category tree and return the results very fast.
- Ariadne should be extendible and scalable.
- Different users should be allowed to access and/or change different sets of data.
In addition we made a few design decisions early on.
The most basic design decision in Ariadne was to use the URL as part of the user interface. Instead of building human readable URL's out of database id's, the id is the path. This means that the Ariadne database has a tree structure, just like a normal filesystem.
The other basic design choice was to use objects as a storage layer, commonly known as an object store. Instead of creating table rows and searching through complex relational database structures, Ariadne stores everything as serialized objects in a single table. In addition it creates indexes, called properties, allowing fast searches through this store.
From these combined choices and requirements almost all other decisions follow. To satisfy the first requirement, the object store is changed into what we call a 'structured object store'. This adds an hierarchic, filesystem-like access layer. This access layer is designed to feel like a normal filesystem layer, with methods like ls, get and find.
2. Ariadne design
Ariadne is divided into a number of modules, each with a specific task.
When a request for an Ariadne object via http comes in, the loader module figures out which object, which template and what arguments are requested. The loader initializes the store module and tells it to call this object with the template and arguments.
The store module retrieves the object from the object store, initializes it and tells the object to run the template.
The object then starts the template, passing the arguments.
The template runs, displays or returns 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.
Templates may call the store to retrieve and initialize other objects and call templates on those. This uses exactly the same calls that the loader used to instantiate the first object.
Templates are the only part of this system to generate output and templates are under complete control of the programmer / designer.
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
This is what happens: 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 template.
The loader is the interface of Ariadne to the web. It manages the input and output, sessions, caching, arguments, HTTP headers, etc. 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 such a page exists and is not expired, this page will be shown and no call to the store is made at all.
There are other loaders, which manage interfaces to other protocols, e.g. FTP, WEBDAV, SOAP, etc.
2.2. Store
The store is the core of Ariadne. This module abstracts the actual mechanism used to store objects. This can be a relational database or an object store or even a flat file. 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:
- call
- get
- ls
- parents
- find
- save
- delete
2.2.1 call
call($template, $arguments, $objects)
Call takes a list of objects, instantiates each one and calls a template on them. e.g:
$store->call($template, $args, $store->get("/asubstore/anobject/"));
The format for $objects is the same as the result format of 'get', 'ls', 'parents' and 'find'. The exact type or contents of this argument is not specified and cannot be relied upon.
By seperating the search functionality from the calling functionality, the store layer becomes much more flexible.
2.2.2 get
get($path)
This function must return the object which has $path as a node.
The result format must be the same as the input format for $objects in 'call'.
2.2.3 ls
ls($path)
This function must return all objects with the node $path as direct parent.
The result format must be the same as the input format for $objects in 'call'.
2.2.4 parents
parents($path)
Parents must return all objects, in the same store, with a node 'above' $path. If no such objects exist, either the path is invalid, or the calling object is itself the root of the tree.
The result format must be the same as the input format for $objects in 'call'.
2.2.5 find
find($path, $query)
Find must return all objects with a node starting with $path, which match the given query.
The query string has a format which looks a bit like SQL, but simplified. It only allows searches, not updates, deletes or inserts. A query can contain one or more property names, operators and search values, as well as ordering and paging. e.g.
$query = "name.value ~= 'A%' and object.priority>=0 order by name.value ASC";
The result format must be the same as the input format for $objects in 'call'.
2.2.6 save
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 instantiated 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.7. delete
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.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 flexible storage of the entire object data. As searches are restricted to properties, all other objectdata need not be searchable and can therefore be stored in any form the objecttype defines. This means you don't need to create new table or object definitions to store extra information in objects.
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.
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.
Ariadne defines two layers of templates, called system or disk templates and user or pinp templates. System templates can contain any valid PHP code and HTML, but they must be saved on disk, in the directory for one of the base Ariadne types. User templates can be defined and edited in the Ariadne user interface. They can be set on any type, user defined or not, and they can be set or redefined at any point in the object hierarchy. User templates are only available in the part of the tree 'below' their definition point. User templates may only use a subset of PHP though, access to the file system, among other things, is blocked. This subset is called PINP.