SyntaxCMS Architecture

SyntaxCMS is a content management system built upon the Syntax framework. A generic install of SyntaxCMS will provide an out-of-the-box web site ready for customization and content; however, the strength of SyntaxCMS is in the modular architecture that provides a platform for extending existing components and creating new components.

In the default SyntaxCMS installation, the business logic entities (datatypes) have already been defined, given [somewhat generic] attributes, and have been rendered as Syntax entity classes. While SyntaxCMS presents a largely "static" database setup, it is built to be a modular and easily customizable system.

SyntaxCMS takes a comprehensive approach to site organization and navigation. The primary mode of navigation for an SyntaxCMS site is via "site sections" which are database entities of the SECTION (aka NODE) datatype. The heirarchical NODEREL relationships system is used exclusively for navigation. The secondary mode of navigation is via the Module system. Modules are what provide the content of the site, and they can (probably most commonly) be included within sections or more developed modules can be accessed directly through the URL.

A Request class system exists to handle the different navigation modes — all nav-specific modes extend a base Request class which provides the basic URI->PHP handler toolkit.

Below is a rough sketch describing the round-trip starting with the URI request from the browser, and ending with the final (e.g. HTML) result delivered to the browser.

request path

File Placement

Only the files needing to be directly addressed by the webserver are placed in the public root. These files are load.php, files.php, error.php, the images directory, and styles.css. All others (templates, init, libraries, config, modules, etc.) are in /private.

Notes on Template Choice

SyntaxCMS does not use a templating engine.

Template engines do enforce logic and presentation separation for lazy programmers, but any template engine powerful enough to meet most needs would be vulnerable to becoming a substitute for PHP, which already does a fine job of mixing HTML and code.

Template Arrangement

Headers and footers for subpages, by default, are in the templates/header.tpl and templates/footer.tpl templates. The home page is in its own template at templates/section/_home.tpl (see Sections below).

Request and Scope

The Request class (and subclasses) and the instantiated $Request object is a central player in building SyntaxCMS components. The Request class stores the current script environment — e.g. variables from $_GET and $_POST as well as other information about the page, including page title, window title, breadcrumb trail, etc. The object may be of a couple types: ModuleRequest and SectionRequest which each have customized methods for building breadcrumb trails. If a new mode of navigation were added to the SyntaxCMS system, there would probably need to be a new Request subclass for that navigation mode. The top-level request is stored in the static Site class.

// GET TOP-LEVEL Request
$TopRequest = &Site::getRequest();

The concept of scope is very central to the modular structure of SyntaxCMS. As mentioned the SyntaxCMS system is built of content providing modules and organizing sections. Both modules and sections can be embedded within other modules and sections. The default behavior is for modules to be embedded within sections — of course the more developed modules can be accessed directly: e.g. the directory is a complex module, with searching capabilities, etc. and while directory items can be pulled into a section template, there is also the ability to use the directory as an application in itself.

The ability to include modules and sections within other templates is accomplished through two key SyntaxCMS methods: F1CMS::module() and F1CMS::section(). Both these methods create a new scope for the requested module and create a new instance of the $Request variable. Some values are inherited from the top-level $Request object — this is currently done manually by the module() and section() methods — e.g. the module() method adds a 'section' attribute to the new $Request object if the top-level $Request object was a SectionRequest class.

Modules

SyntaxCMS is a collection of modules that are combined within a site navigation and template framework. A SyntaxCMS module can be as simple as a datatype or as complex as a web application that depends on several inter-related datatypes. There is no inherent db-module relationship (i.e. you could also create a module that does not make any db calls), but there is an implicit understanding that modules are responsible for the content of your site.

There is a central, static Modules class with which modules are registered at script run time. Each module is not an object instance, but rather a set of array attributes in a object attribute of the Modules class. The adding of modules is accomplished in a configuration file (config/user.conf.php). When a module is added, the module's capabilities must also be added. These capabilities are always checked before allowing a section to make a request of a module. Also, other attributes may be specified for a module — e.g. the 'navigable' attribute is currently used to determine whether the module should appear in the "functional navigation" (secondary mode of navigating the site). For example:

Modules::addModule('document', 'Documents');
Modules::addCapability('document', 'list');
Modules::addCapability('document', 'detail');
Modules::addAttribute('document', 'navigable', false);

There is an assumption that module names (i.e. 'document' in example above) are unique. If you add a new module with the same name as a previous module, it will be overwritten.

An important feature of the Modules class is the ability to register a generic module — which will be used if the requested module doesn't exist. This ability is central to SyntaxCMS, which uses the generic module to fetch all content (i.e. content agnostic) results for a given section.

Modules::addModule('generic', 'Site Content');
Modules::addCapability('_generic', 'list');
// specify that this module is the generic module
Modules::regGenericModule('_generic');

The Modules class also provides the ability to register "fallback providers" which are Modules that are substituted in when an action (i.e. capability) is requested that doesn't exist for the specified module. This means that you could define a number of modules that used a common datatype, and the "list" capability might be the same for all of them. For example:

// specify that the "list" action being sent to person module 
// should be handled by the directory module
Modules::regFallbackProvider('person', 'directory', 'list');

Conventions

There are several conventions that a module mustfollow in order to be loaded correctly.

  1. Modules are locate in the modules/ folder, and the module directory name must correspond to the 'name' attribute of that module: e.g.'document' module is expected to be found at modules/document/.
  2. Modules must have a modules/{module}/init/register.php script, which will include_once() any libraries, define() any constants, and perform any other tasks that are relevant for the system on the whole. When a module is loaded in the site's master init.php script, the register.php script is executed for that module. This enables one module to be aware of other modules — and take advantages of libraries they include.
  3. Module actions are expected to correspond to PHP scripts in the module directory: modules/{module}/{action}.php — e.g. a request for $module='document', $action='list' would result in loading the script modules/document/list.php.

In addition, any modules that are set to be publicly exposed must contain a listitem.tpl template that uses a pxdb_record object named $Record.

Invoking Modules

Browser / URL

The most easily explained means of invoking a module action is via URL. Assuming my SyntaxCMS site is http://syntaxdev.forumone.com/, I can perform a request for the 'list' action on the 'document' module by accessing the following URL: http://syntaxdev.forumone.com/content/document/list/. I can pass any additional useful parameters to the list script by simply appending a querystring to that URL, e.g. http://syntaxdev.forumone.com/content/document/list/?limit=5&keywords=air+water

F1CMS::module(array $params)

The more powerful way to access modules' content is through the F1CMS::module() method. The F1CMS::module() method simulates a request for a module which returns any output (usu. HTML — but could be XML, plain text, or nothing). This means that there is no prescription for what kind of template system each module uses (i.e. default modules are not using any templating system, simply PHP display files that are populated with results provided by the business objects layer).

Here's how this method would be invoked in a section template:

<h1>3 Most Recent Documents</h1>
<?=Content::module(array('module'=>'document', 'action'=>'list', 'limit'=>3))?>

Here's a simplified version of what the module() method does:

function module($params)
{
    // Create a new local-scope $Request object; 
    // module just assumes this is the global $Request object.
    $Request = &new ModuleRequest();
    $Request->setModule($params['module']);
    $Request->setAction($params['action']);
    
    // module script needs to know all params that were passed
    $Request->setVars($params);

    // the module & action are turned into a PHP script request by ModuleRequest
    // that script is included directly in this function sends output to the 
    // buffer.
    include($Request->getScript());
}

 

That same module request could be made through the browser (and the same table would be returned) by accessing the URL: content/document/list?limit=3.

The Generic Module

The generic module deserves a special section because it handles most of the work on the SyntaxCMS site. The generic module contains the core business logic that is used for all of the content on the site.

Because the business logic that determines what content appears on the site (i.e. hasn't expired, is associated with current section, is approved, etc.) is the same for all datatypes, it makes sense to have only one list.php script. The issue, of course, is that while the logic that fetches content is uniform, different datatypes need to display different attributes — i.e. [location] is relevant only for events and [filesize] is only relevant for uploaded documents. The solution to this problem in the SyntaxCMS framework is provided by 1) a convention for naming templates within the different modules and 2) the ability of the PHlexDB collection class to return specific defined objects for content of a given datatype.

So, creating listitem.tpl, listitem-featured.tpl, and detail.tpl templates within your module is all you need to do in order to create a custom look for your module's content.

Uses of Modules

In the example above we would be expecting the F1CMS::module() call to return the resulting HTML (e.g. a tabular list of documents) to the buffer. Of course HTML is not the only option for a module's output - and as hinted at earlier, there is no reason why a module should need to output anything at all. Module actions could conceivably perform any of the following tasks:

  • Apply a template that producess RSS instead of HTML and return the resulting RSS (XML).
  • "Subscribe" a user to a calendar event: content/calendar/event/subscribe?email=hans@forumone.com (a "subscribed" page returned, perhaps.)
  • Log a request or action to a database or log file (no output returned)

Sections

Sections are database entities that serve as navigation nodes — which means you can "go to" a section. Sections exist to combine and organize the content produced by content modules. Sections use a standard template for display, and by default will simply display all site content that has been related to that section. Although Sections use a standard template, a section can have a custom template — a good example of this is the custom home page template — and there are a number of attributes of the Section entity that can make one section look a great deal different from another.

The section.php script handles all requests for a given section. Attributes of the section indicate how content should be grouped by the section template — e.g. by 'topic', by 'issue', or by content type (datatype). Finally the actual template that renders the HTML (or XML or whatever your final product is) can be customized by creating a {URL_ID}.tpl file within the templates/section/ directory. Some examples may help illustrate this. The "Home" Section (top node) has a url_id (`idnum`) of '_home'; to create a custom template you would create the file templates/section/_home.tpl.

Request URI Looks for template ...
/section/news/europe templates/section/news/europe.tpl
/section/news/europe/brussels templates/section/news/europe/brussels.tpl
/ [exception: home page node is named '_home' but is accessible from /] templates/section/_home.tpl

If a given template is not found, it uses the templates/section.tpl template.

Of course the ability to override a template for a given section means that one section can look completely different from another. An example on GE/ETF site is the FOOTER section (a hidden section) is included within another template file and exists solely for the text (e.g. privary statement, copyright, etc. at the bottom of each page).

Invoking Sections

Browser / URL

The most common way to access a section of the site is through the URL. Section URLs are prefixed with the /section/ path. Sections also maintain their heirarchy when represented by URL. The URL ID attribute for a section reflects the component of the URL that corresponds to each section. In other words, a request for the URL: http://syntaxdev.forumone.com/section/about/contact might be translated into a request for a section that is found at the heirarchy: Home > About Us > Contact Information. The pieces of the url — 'about', 'contact' — correspond to the respective URL ID values for the implicated sections.

F1CMS::section(array $params)

Similar to F1CMS::module(), the section() method simulates a request for a section and then returns the content. This is particularly useful in instances where the default section template has been overridden and is going to return a special-purpose template. For example, the GE/ETF project uses a customized footer section for delivering the content that appears at the bottom of the page. The request for this section looks like this:

<?=F1CMS::section(array('request'=>'/_footer/'))?>

(Sections whose URL ID and/or Breadcrumb values begin with '_' are ignored in building Breadcrumb trails and in building navigation heirarchy.)

Module Class Architecture

There is no site-specific module class architecture, but if your module will be providing content that needs to be displayed in section templates, then you will want to use the the core PHlexDB content classes — or create your own classes that extend these (and pxdb::register_class() them as appropriate).

See the tutorial on creating modules for some examples of what this means.

Content Caching

Caching is handled in the methods of the SyntaxCMS class. Only module content and navigation content (which could — and maybe should — be rewritten as modules) are cached. Whether or not a module's content is cached is determined from the 'cache' attribute of the registered module.

To cache the content of the document module:

Modules::setAttribute('document', 'cache', true);

Important: if you include/make a request for a non-to-be-cached module within a cached module template, the whole content will be cached — meaning that the 'cache'=false setting of the included module will be ignored. A more complex caching logic would be possible if we were to use, e.g. Smarty, for template files.

The caching system is integrated with the admin tool. If any new content is added or modified, the entire content cache will be cleared — meaning that there should almost never be an issue of an out-of-date site. Content cache lifetime is defined in init/init.content.php.

The navigation is also cached, and is similarly refreshed if any changes are made to section objects — or to the ordering of those objects.

General Article



Components of SyntaxCMS

(20 Jul 2005)
"Thinking like" a new system is usually one of the hardest hurdles to overcome when adjusting to a new application. This article discusses the conceptual components of a SyntaxCMS site. Learning the names and functions of these components not only makes reading the documentation easier, it helps you begin to think in terms of SyntaxCMS applications.