Architectural overview
This document describes the architecture of CASCO in broad terms, so that the reader will get a bird's eye view of how CASCO is structured. We will start with schematic overviews of the main CASCO components and how they interact. After that we will explain the directory structure of casco. Finally we will describe the major CASCO classes one by one and provide some example code.
Frontcontroller and MVC
Area: the front controller
In CASCO, the Area is the first component that handles a request. Some important responsibilities of an Area are:
- delegating the rendering of the request to the right controller(s)
- combining the controller output into the final rendering and returning
Model-View-Controller
Like many other frameworks, CASCO implements the MVC, or Model-View-Controller, design pattern.
In a nutshell, MVC is a way to separate flow, data/business logic and presentation. The main advantage of MVC is that you can switch to a different implementation of one of its three components without having to change the other two. For example: Changing the look and feel of your website while keeping site structure, data and business logic the same.
If you are not familiar with this concept, it may be a good idea to read a bit more about it.
The CASCO architects have built upon MVC and extended it with two more concepts: Layers and Plugins.
Layers:
Introduction to CASCO layers
In CASCO, the model component of MVC has been devided into two subconcepts: model and storage. The reason for this is to isolate business logic and data storage, taking MVC a step further.
It is important to be aware of the concept of layers when working with casco. As we will show you later layers are the key to extending CASCO....
Currently, CASCO core (only the essential modules) ships with three layer implementations: HTML, MODEL and STORAGE.
HTML layer
This layer describes interfaces for rendering html output.
MODEL layer
This layer describes interfaces for modelling business logic and data structures.
STORAGE layer
This layer describes interfaces for data storage and retrieval.
The storage layer has been designed as a component of Model. This is because we want all storage transactions to be managed by the Model itself, not directly from a controller. ??why??
The storage layer contains two main abstractions
- database access abstraction
- database dialect abstraction
By default, the Storage layer in CASCO is implemented using SQL.
overriding existing layers
CASCO allows you, the programmer, to override an existing layer to taylor it for your specific needs.
For example, as stated before, the Storage layer in CASCO is implemented as SQL, but this is a plain ISO SQL implementation. However, you will need at least a database-specific connector class that implements the database abstraction strategy, and most SQL databases require extensions because ISO SQL doesn't contain everything required to build a complete application. For this reason you need to register either the casco_db_sql_mysql or casco_db_sql_pgsql modules. These modules contain subclasses of Storage/SQL classes (or implementations of the abstraction classes / interfaces) that need different behavior for MySQL/PostgreSQL. When registering one of these module, CASCO is told that whenever classes in the Storage layer are needed, subclasses from the MySQL/PostgreSQL module are actually instantiated.
For the observant reader: yes, CASCO supports a complete database dialect abstraction!
but wait .. I want my own layer!
The three core layers are not final. In CASCO you can define your own layers. This is one of the concepts that make CASCO truly extensible, one of the DesignGoals. Adding a new layer is a topic for another page, but you can imagine creating a layer for presentation in another format (pdf, xml, ..) or storage in another format (csv, xml, ..) or for controlling your coffee maker.
Plugins:
Introduction to plugins
The second 'amendment' to MVC in CASCO is the concept of Plugins. This is another example of the concepts that make CASCO truly extensible.
When writing a new Plugin, you are actually describing the behavior of a custom type in CASCO.
The Plugin cuts through all layers defined in CASCO. You will describe how the plugin should present itself when used in the HTML layer, what business rules it must submit to and how it should put inself into and out of storage.
When CASCO is extended with such a new plugin, any Model you implement can include one or more instances of this custom type.
CASCO core ships with a number of predefined Plugins, such as:
- text and text_multiline
- integer
- boolean
- select and select_multi (enumeration types)
- date, date_time and timestamp
- id
- password and password_salted
These are pretty basic database type plugins and you might conclude from this 'smell' that plugins are tightly coupled with databases. Indeed, in 98% of the cases you *will* need a type that can be validated, stored and presented. However, this is not a requirement. As stated before, Plugins cut through the layers in CASCO (by default: html, model and storage). But that does not mean they are *required* to implement all layers. Plugins may be used just for presentational purposes ... or for expressing a business rule not related to stored data ...
You may want to look at the collection of plugins as your modellers toolkit. Pretty much any aspect of modelling should be implementable in a Plugin, provided there is a layer for that type of aspect.
Basic directory structure
As we have shown you the basic architecture of Casco is pretty simple. Now we will discuss how this architecture is organized in terms of directory structure and classes.
In explaining the architecture in terms of classes we will build an example application, but not by hooking into the framework using subclassing. This code is intended to illustrate the principles behind the Casco architecture. Extending the rich classes provided by Casco itself would only obscure our examples. Instead, we show how these core Casco classes could have been implemented. (If you want to know how to best make use of the power of Casco as intended, please read one of the tutorials.)
We'll be making a simple blogging app.
First of all, we will describe the directory structure.
Any Casco application consists solely of modules and some configuration. Most of the modules are part of the framework, but the application itself is also implemented as a module (or set of modules). It is good practise to put framework modules and application modules in separate directories (in this example 'casco_core' and 'myblog').
webroot
|
|- inc
| |- config.inc.php
| |- local.inc.php
|
|- lib
|- casco_core
| |- casco
| | |- area
| | |- controller
| | |- view
| | |- model
| | |- lib
| | |- plugin
| |- casco_core_db_sql
| | |- area
| | |- model
| | |- lib
| | |- plugin
| |- casco_db_sql_mysql
| | |- area
| | |- model
| | |- lib
| | |- plugin
| |- casco_db_sql_pgsql
| | |- area
| | |- model
| | |- lib
| | |- plugin
| |- some_other_module
| |- area
| |- controller
| |- model
| |- lib
| |- plugin
|
|- myblog
| |- area
| |- controller
| |- view
| |- model
| |- lib
| |- plugin
All modules that will be used by an application must be registered and configured in config.inc.php. Registration is required because stock Casco comes with many modules out of the box, and not every module will be used by every application. The local.inc.php file is automatically included by config.inc.php and contains settings that differ per deployment (database configuration, email server settings, etc)
The directory structure of a module is the same for every module: It contains directories for every major Casco class type that it implements. The CASCO class types will look familiar if you have read the first part of this overview. They are: area, controller, view, model and plugin. In fact there are two more class types in CASCO: lib and skin, but we will tell you a bit more on those later on..
Let's zoom in a bit on the Casco tree:
|- casco | |- area | |- controller | |- view | | |- class.view.php | | |- view_html | | | |- class.view_html.php | | | |- tpl | | | ... | | |- view_list | | | |- class.view_list.php | | | |- tpl | | | |- heading.tpl.php | | | |- record.tpl.php | | | ... | | ... | |- model | |- lib | | |- finder | | |- class.finder.php | | |- test | | |- class.test_finder.php | |- plugin
We can see here that Casco implements all class types (except for skin), and it contains several classes of each class type. When a class of a certain class type is instantiated, this is done using <classtype>::create('<classname>'). For example, when we need an instance of view_list, we would write this code:
$myview = view::create('view_list');
If we just want to load the class and not instantiate it, we could use a similar call:
view::load('view_list')
When you use either of these functions, Casco proceeds to use the Finder to find this class. It will look in all registered modules for a file named class.view_list.php, either directly under the view directory or under the view/view_list subdirectory. Classes can be put inside their own subdirectories because they may have tests, images, templates or extra helper library classes associated with them. If this is not the case, you don't need the useles directory and you can just put it directly under the directory for the class type.
More on class types
Let's expand a little on the classtypes. We have seen their place in the architecture schematics and we have seen their place in the directory structure. Now let's take a closer look at each of them:
Area
In our hypothetical blogging application, we can identify two major areas: the front-end and the back-end. The front-end would be the area that visitors see, and the back-end would be used for writing new blog posts.
Areas are a place to stick configuration in. It contains so-called "page definitions", which define what pages the user can visit. Pages are the lowest level URL handlers that you can directly define in Casco. When they are requested by the user, controllers handle the rendering of them.
The Area also holds the main HTML template for that specific area and maps the HTML blocks that need to be generated dynamically to so-called containers. These containers then are passed along and finally return rendered and ready to merge with the Area template.
In order to render a page, controllers get page definitions as input from the calling areas, and create HTML as output. The HTML is put in the forementioned containers.
To summarize: an area defines the containers which are filled in by the controller. This is part of the contract between an area and the controllers it can call.
Here's the code for a super-simplified area:
class frontend_area
{
function render()
{
$controller = controller::create('controller_blogpost');
// This area just knows about one page
$page = array('model' => 'blogpost');
$containers = array('head' => '', 'body' => '');
$data = array(); // No extra input needed
$containers = $controller->process($page, $data, $containers);
return casco::templator()->render_tpl('layout', $containers, 'area', 'frontend_area');
}
}
We can see here that controller_blogpost is found and instantiated by Casco. Then an extremely simple page definition is created containing only the model, which is the only thing that this controller really needs. Next, it defines the containers to be used and initializes them, and calls the controller's process function. This function is the entry point for all controllers. The filled-in containers are then caught from the return value of the function call, and used as input variables for the template to render. This template would look something like this:
<html>
<head>
<title>An example</title>
<?= $vars['head'] ?>
</head>
<body>
<h1>Here's a very simple page</h1>
<?= $vars['body'] ?>
</body>
</html>
The template file would be found under area/myarea/tpl/layout.tpl.php and the class file under area/myarea/class.myarea.php, in our application's module.
When you implement a real-world area instead of this super simplified version you will extend from one of the area's that ship with CASCO. In that case you will find that the Area has one more neat responsibility:
The base Area class in CASCO isolates sessions between different area's. That means you can log into the front-end as a regular user and into the back-end as an admin at the same time using the same browser! Something most frameworks or CMS's do NOT support.
Controller
In broad terms, controllers contain the business logic necessary for obtaining data from the model, and they call views to render this data.
Extremely simple pages can also be rendered directly from controllers without going through the overhead of instantiating a view class, but in general this is discouraged.
The controller we have above could look like this:
class mycontroller
{
function process($page, $data, $containers)
{
$model = model::create($page['model']);
$view = view::create('blog_entry_view');
$data['records'] = $model->select();
$containers = $view->process($page, $data, $containers);
return $containers;
}
}
This code should look very familiar by now. We just pass on the containers we got to the view. Usually this is the case, but in some cases we may want to create a new set of containers for the view to fill, which we then recombine in a template and stuff into the containers we get from the area. We also pass the records from the model as data to the view.
Notice that this controller is already quite generic: it does not know anything about the model it should render. We could make it more generic by also passing the map in the page definition and even more generic by making the view configurable. This is exactly what the controllers shipped with Casco allow you to do.
model
The model is the most important class in Casco, because it is the hub of communication between all the other classes. The model contains the data and the other classes exist only to access (create, view, list, delete) this data.
We're not going to create our own data model class from scratch this time, but we're going to define our model using the existing model:
class blogpost extends model
{
function fields()
{
$fields = array();
$field = array();
$field['name'] = 'id';
$field['plugin'] = 'id';
$fields[] = $field;
$field = array();
$field['name'] = 'title';
$field['plugin'] = 'text';
$field['size'] = 100;
$fields[] = $field;
$field = array();
$field['name'] = 'contents';
$field['plugin'] = 'text_multiline';
$fields[] = $field;
return $fields;
}
}
The fields function is the only function you must define in a model, because a model is useless without fields. In models with a SQL storage component, fields map to database columns and the model maps to a table. As can be seen, fields always have a name (maps to the column name) and a plugin. The plugin class is described next.
plugin
As we've seen before, the plugin is one of the classes responsible for CASCO's versatility. It brings together the view and model layers of the MVC pattern, plus our own extension to MVC: the storage layer. To recap, a plugin is used for every operation that pertains to a field: Validation, managing SQL expressions that add constraints to this field, rendering HTML output etc.
Because a plugin does so much, we do not want to put all this functionality in one class, since this would easily result in a huge class of several thousand lines of code! It would also go against the modularity of Casco: How do you add a new type of output (for example: PDF output) to an existing class?
To solve these problems, plugins are split up into components which implement all the different layers we've just identified. All plugins implement the model layer; this contains validation and anything else that purely has to do with the model abstraction. There's also a 'html' layer by default and when you register the SQL module, a 'sql' layer is added to all plugins.
More on how CASCO manages Composition can be read here
Everything related to plugins (including the different components) is put under the plugin directory. Here's a partial zoom-in of this tree:
|- casco
| |- plugin
| |- plugin
| | |- class.plugin.php
| |
| |- component_html
| | |- class.component_html.php
| | |- tpl
| | |- name.tpl.php
| | |- value.tpl.php
| | |- form_element.tpl.php
| | ...
| |- text
| | |- class.text.php
| |
| |- component_html_text
| | |- class.component_html_text.php
| |
| |- text_multiline
| | |- class.text_multiline.php
| |
| |- component_html_textarea
| |- class.component_html_textarea
| |- tpl
| |- form_element.tpl.php
|
|- casco_core_db_sql
|- plugin
|- component_sql
| |- class.component_sql.php
| |- tpl
| |- greater.tpl.php
| |- equals.tpl.php
| ...
|- component_sql_varchar
|- class.component_sql_varchar.php
|- tpl
|- sqltype.tpl.php
Let's look at the text_multiline plugin. It inherits from text, which itself inherits from plugin. The html layer component of text_multiline only redefines the form element (using a <textarea> instead of an <input type="text">) and inherits all other templates from the html layer of its parent class (text). It inherits its SQL layer component from its parent class, since both multi-line and single-line text fields can be represented by a VARCHAR, so this is component_sql_varchar.
Templates to create SQL expressions involving fields are always the same no matter what the type of the column, so all expression templates are put in the base component_sql class to be re-used by all subclasses. The rendering of the SQL CREATE statement is different for each type, so all SQL components contain a sqltype.tpl.php template.
Now we have the necessary background to understand what the view does, let's have a look at that.
view
Views are very simple classes that exist purely because we do not want logic inside our templates. Things like looping and conditionals are commonly needed when rendering templates, so the view class is an excellent place to put this logic. It allows us to keep our templates pure and simple. Here's what our view looks like:
class blog_entry_view
{
function process($page, $data, $containers)
{
foreach($data['records'] as $record)
{
$data['record'] = $record;
$containers = $this->render_record($page, $data, $containers);
}
return $containers;
}
function render_record($page, $data, $containers)
{
$vars = array(); // For our template
$model = model::create($page['model']);
foreach($model->get_map() as $field)
{
$plugin = plugin::create($field['plugin']);
$plugin_data = $data['record'][$field['name']];
$plugin_containers = $plugin->call('html', 'process', $field, $plugin_data, $plugin_containers);
$containers['head'] .= $plugin_containers['head'];
$vars[$field['name']] .= $plugin_containers['body'];
}
$containers['body'] .= casco::templator()->render_tpl('entry', $vars, 'view', 'myview');
return $containers;
}
}
The process function is pretty simple: it just calls render_record for each record, which will render this template for each record:
<div class="blog-entry"> <h2><?= $vars['title'] ?></h2> <p><?= $vars['contents'] ?></p> </div>
The contents of render_record are less simple: It loops through all fields in the current map, and lets each plugin render the value of the field it manages, in the HTML layer. If we wanted to create a view for rendering PDF documents, all we would need to do is call the plugin's pdf layer instead, and change the names of the containers to appropriate ones for PDF (because the area would expect different variables in its template).
skin
...the emperor's new clothes...
A quick word about the elusive 6th CASCO class type, Skin. Skin is without question the smallest class that deserved its own class type directory. It simply defines a skin directory and its place in the Skin hierarchy. More on this someday on a separate wiki page...
lib
...there is another...
O yes, before we forget: there's still that lib classtype. This can contain any kind of helper class that does not fit in the above categories. There's not much else to say about that :)
Examples of Lib classes are many of the classes used internally by CASCO, such as:
- Finder
- Translator
- Templator
- Cache
- Url
- Casco_mail
- Casco_date
- etc....
Attachments
- architectural overview 1.png (22.7 kB) -
architectural overview 1
, added by Lex on 08/07/08 04:57:59. - architectural overview 2.png (27.2 kB) -
architectural overview 2
, added by Lex on 08/07/08 08:35:34. - architectural overview.png (35.4 kB) -
architectural overview
, added by Lex on 08/07/08 08:39:17. - architectural details model and plugin.png (45.5 kB) -
architectural details model and plugin
, added by Lex on 08/08/08 04:25:46. - architectural details area.png (30.2 kB) -
architectural details area
, added by Lex on 08/08/08 05:13:32. - architectural details controller.png (29.9 kB) -
architectural details controller
, added by Lex on 08/08/08 05:27:04. - architectural details view.png (54.2 kB) -
architectural details view
, added by Lex on 08/08/08 05:34:18.







