Chapter 2: Skel's Pieces—A Bird's Eye View

Project Abandoned

Warning! This project was really fun and a learned a lot, but it has now been abandoned. Feel free to rummage around here among the bones, but don't expect to find anything living here!

Arguably one of the most difficult aspects of understanding Skel is zooming out and taking in the entire landscape of interfaces and how they interact. This is because Skel is actually a tiny beginning of the project to map out all possible program interfaces. It is a taxonomy project, similar to biologists' attempts to define and describe species.

Currently, though, Skel focuses on a very narrow range of interfaces—something like "primates" in our biology example. The category of primates is relatively small, and the interactions among primates are relatively easy to define. In the same way, Skel currently deals only with interfaces having to do with web applications frameworks.

"Zooming out" too far, then, is equivalent to trying to figure out how all organisms on Earth interact and affect each other—clearly a fruitless pursuit in all but the most ambitious of cases. While this will eventually be a useful exercise, currently it's not.

Still, with this chapter I'd like to attempt to at least describe the Skel interfaces at a high level in the narrow scope of web applications frameworks. This is, of course, not the only scope in which the interfaces are pertinent, but it is the current use case, and should provide an idea of how they can be useful.

Overview of Interfaces

After reading the following descriptions, you should have an idea of the roles and responsibilities of the components each interface represents, and how the components work together to create an efficient, flexible and loosely-coupled web application. You should understand which features are expected to be built into the framework that the interfaces define and which features are to be implemented by each specific application that uses the framework.

Without further ado....

Component

Component is the humble provider of almost all of the benefit of the Skel concept. Component is effectively the communications protocol that allows application logic to remain reusable in contexts never imagined by the application's creators.

A Component is simply a coupling of data with a method for displaying it (a Template). By packaging these two elements together, the data can remain structured and changable all the way up to the point of final render on screen, while at the same time allowing any application logic that has a preference about how the data is displayed to affect that. This allows libraries to build upon one another, passing Component objects through, adding, changing and removing fields, changing display templates, etc.

In the real world, a Component will often represent a formal data object (DataClass, in Skel speak) that maps back to a database. It certainly doesn't have to, though. In fact, at its most basic, a Component is a simple array with no requirements about what data it holds. This allows for the creation on the fly of anonymous Components, allowing programmers to create simple, lightweight applications without the burden of too many formal definitions. (This lack of definition does have its downsides, but so far it has been more advantageous than not.)

Methods

Components' data elements are set and retrieved using array notation (which requires implementation of PHP's ArrayAccess interface, the interface from which Component is extended). On top of that, a Component has only three methods: getTemplate, setTemplate and render, all of which do exactly what you would expect. Since a Component is a very natural representation of a standard tree structure, it is assumped that render is a recursive call that flattens the tree into a single string for display. (A Template doesn't have to just return a string, though, as we'll explore later.)

Interacts With....

A Component is usually generated by an App method, a Db, or a Cms. This is because Dbs and Cmses are responsible for retrieving structured data objects that often occupy a position on screen, and an App fits these structured data objects into more general structured data objects that form the user interface as a whole.

Component also requires a Template instance to render into.

In general, Component's primary domain is as a medium for the manipulation and display of data within an App, and so it will primarily be used in the context of objects that generate and manipulate displayable data.

Related Minor Interfaces
DefinedComponent

DefinedComponent simply allows you to create a list of "known fields" that other objects (like an ORM system) can use to manipulate the object.

ComponentCollection

A ComponentCollection, as the name implies, is a collection of Components. It has methods like filter and contains that allow you to query the Components it manages. This is useful for building a sort of "poor man's database cache".

It is intended to be renderable, and the official implementations offer a renderable version which simply concatenates all rendered components using newlines. However, this behavior is not defined by the interface itself.

Examples

At its simplest, a Component may be created and rendered like so:

<?php

$me = new PersonComponent();

$me['name'] = 'Joe Aristocrat';
$me['profession'] = 'Governor';
$me['age'] = 38;
$me['uniqueId'] = 'joe-aristocrat1';

$me->setTemplate(new StringTemplate('
  <div class="person" data-uniqueId="##uniqueId##">
    <p class="name">##name## (##age##)</p>
    <p class="profession">##profession##</p>
  </div>')
);

echo $me->render();

Later on, another app might use this data to add a yearOfBirth property to the component, to parse out the first and last name, and to specify a work category based on the profession. It might then update the template with a new template that can display all of the extra data as well as the original data.


App

While Component is the data transport medium in the Skel world, App is the actual structure and backbone on which libraries are created. Since App extends Context, which extends Observable, all implementations of App are capable of registering event listeners and also of serving as a Context for methods that require one (more below).

Because of this, an App (which can also be considered an application library) can fire its own custom events and request and/or provide specific strings, allowing it to function as a submodules of other apps.

App's primary domain, though, is as a bridge between the user interface and the datalayer. In classical MVC, this is the controller. I've broken with this terminology, though, because I thought it was a little too limiting. Since there's much that the App can do to affect the display of view components, and since the datalayer is not really a "model" in the classical sense, it didn't seem to make sense to mash Skel into the description of an MVC pattern.

It's important to remember that an App is not limited to serving a web interface across an HTTP connection. In fact, one of the primary motivations for building Skel was to encourage the creation of application libraries that could do more than just serve web interfaces. To this end, App is meant to be the container for custom application logic, and to use that logic to serve up its primary interface in the form of nested Components, as mentioned in the previous section.

Methods

The primary method used in App is getResponse. This method accepts a Request object and returns a Response object. This is the "soup to nuts" method, typically calling on the Router to route the request and possibly generating errors or initiating a redirect. It's also a ripe place for events to fire. In the official implementation, there are a number of events that fire, including BeforeRouting, ComponentCreated and ResponseCreated. These allow third parties to hook into the execution and affect certain aspects of the program (the Request that the app is executing on, for example, or the Component returned by the app logic to be turned into a response).

Other methods include str for getting strings, getError (for getting a pre-composed error Component), and redirect, among others.

Interacts With....

In its capacity as bridge, an App will often coordinate the use of a Db and a Cms and require a Config instance that will supply it with information about where to find front-end materials like templates and images. Other types of Apps (like AccessControlledApp, for example) will likely coordinate other systems, such as User Authentication systems and Permissions systems, which are yet to be created.

As mentioned previously, an App is fed a Request object and returns a Response object. The custom application logic for each specific app will also return Component objects, and will very likely create various types of Template objects.

This is the large and diverse family of the App.

Related Minor Interfaces
Request and Response

These are two interfaces based loosely on Symfony's Request and Response HTTP abstraction classes. While they're nominally important, they won't get much of a mention here in the docs. This is in part because I'm not totally confident in the role they'll eventually play, and in part because they're more or less just reduced wrappers for the Symfony classes, which are already documented.

Router and Route

These are two other minor interfaces used by App. Because these are fairly standard in the world of applications frameworks, they don't really need much depth here. Suffice it to say that a Router simply manages a collection of Routes, and a Route is what connects a given Request to the functionality defined in the Route (returning a Component to whatever executed it).

Examples

Because in a way App is meant to be a skeleton for a class implementing the Template Method pattern, it's hard to give a useful example of it. Still, there are a few principles that may come in handy. Consider the following:

<?php

// Initialize everything

$config = new \MyBrand\Config('app-config');
$app = new \MyBrand\App(
  $config,
  new \Skel\Router(),
  new \MyBrand\Db($config),
  new \MyBrand\Cms($config)
);

// Now connect a few things...

$app
  ->registerListener('Error', $app, 'prepareUiForError')
  ->registerListener('ComponentCreated', $app, 'prepareSiteComponent')
  ->getRouter()
    ->addRoute(new \Skel\Route('/{section}/*', $app, 'getNormalPage', 'GET', 'page'))
    ->addRoute(new \Skel\Route('/', $app, 'getPage', 'GET', 'home'))
;

// Note: `\Skel\Request::createFromGlobals` is a function from Symfony's `Request` implementation,
// which I've used as the basis of the Skel `Request` class
$response = $app->getResponse(\Skel\Request::createFromGlobals());

// Do some more stuff to `$response` if desired, or just send....
$response->send();

There are several important points demonstrated by the above example:

  • It is assumed that each actual application will be a custom-coded extension of a base App implementation (like the official Skel implementation). In this case, \MyBrand\App is the concrete class being used, and it is assumed that all application logic is contained therein.
  • Because each app's requirements are different, no assumptions are made about the constructor in the interface definition. Thus, it is up to each app implementation to require the correct dependencies in its constructor. In this case, the app requires a Config, a Router, a Db, and a Cms.
  • Note that every class that requires a configuration is fed the same instance of the \MyBrand\Config class. This only works if the \MyBrand\Config class implements all of the interfaces required by the various other classes, and this is the whole point. If the class doesn't implement, for example, the CmsConfig interface, an exception will be thrown when you try to hand the Cms constructor the $config instance.
  • Further below, the app's registerListener method is used to insert custom logic at certain points. Specifically, when an Error is generated by the app, the code defined in the app's prepareUiForError method is run. The app will also run some preparatory code when the final Component is returned from the method called by the Router.
  • In this case, the router is retrieved after the app is all set up and routes are assigned. Of course, in certain cases it may be advantageous to define the routes in a method of the application itself, which can be used as a setup method that can be called from various contexts.
  • Finally, a Response is retrieved and sent back to the client. This could have been accomplished in one line, but it was split up to demonstrate that it's possible to further manipulate the response, if desired.

Config

The Config interfaces are the result of an interesting idea I had to try to make configurations more formal while still offering the possibility of localized configuration tweaks and special stubs and mocks for testing.

In short, each configurable component (App, Db, Cms, User, whatever....) defines its required configurations as methods in a special *Config interface. Then you implement all of the necessary *Config interfaces in one single Config class (which may be extended from a base class that implements certain of the configuration methods).

The point here is not to hard-code your configurations into the class methods, since that wouldn't allow you the flexibility of easy local config variations. The point is that each configurable component requires a config object with a known set of config-getting methods, and if your Config class doesn't implement all of the necessary methods, you get a warning (up front, rather than something way down the line that quietly messes up when your users are doing something important).

The other advantage to this, as demonstrated by the offical Config implementation, is that you can create a method that calls each config method individually to verify that they all return something useful. This can alert you up front to missing configuration keys.

Methods

These necessarily change according to what you need configurations for, but the one universal configuration method is getExecutionProfile. This should tell you whether you're in beta, production, testing, or whatever other environments you define.

The other two basic methods are checkConfig and dump, both of which are debugging methods to help figure out whether you have configuration issues and what they are.

Beyond that, each derivative interface defines getter methods that should return the configuration needed for whatever they were created for. For example, AppConfig defines getContextRoot, getPublicRoot and getTemplateDir—all methods that tell an app where its important directories are located.

Interacts With....

This can interact with any class that might require a changable config. These tend to be the "system" classes, like App, Db, and Cms, though it's conceivable that you might want to change the way other classes work using a config (allowing or disallowing the creation of anonymous Components, for example).

Examples
<?php

// You've ostensibly implemented the necessary `Config` interfaces in
// your own custom `Config` class in your `MyBrand` namespace
$config = new \MyBrand\Config('app-config');

// If we're in beta, check the config for errors
if ($config->getExecutionProfile == $config::PROF_BETA) $config->checkConfig();

$db = new \MyBrand\Db($config);
$cms = new \MyBrand\Cms($config);
$router = new \Skel\Router();
$app = new \MyBrand\App($config, $router, $db, $cms);

// Do something....

Db

Db and many of the related data interfaces are arguably the least defined of all of the interfaces. This is largely due to the fact that they're intended to be declarative classes, and it's impossible to know what data one might want in an application. Still, there are a select few data needs which are more or less common among applications, and these are strings and menu items. Thus, while Db itself defines only two methods, both for getting strings, and AppDb (a derivative interface) further defines only one method, getMenuItems, it is assumed that the concrete class that is created to handle the data needs of your app will define a few more methods. That said, there is a reason that no query method was defined, and this is because there are real advantages to a declarative data layer. While you can certainly opt to create a standard database abstraction layer—complete with your own special query language and builders, etc.—the official Skel idea is to stay declarative. (There are a few details in all this, discussed below.)

Methods

As mentioned, general datalayer methods are scant.

Interacts With....

Db classes will almost always interact mostly with App classes (i.e., libraries). Since App is the basic unit of organization for vendor-specific functionality, and Db is the basic declarative data abstraction layer, the two work together to provide functionality that's decoupled from user interface, and data that's decoupled from storage mechanism.


Cms

Cms is a special sort of database. Because "content" is a more clearly defined concept than just general data, and because we typically want to do specific things with content (like get it using a public address), it's possible to define a CMS much more fully than a general database.

In the case of Skel's CMS, it is also an ORM, and the Cms interface extends the Orm interface.

Methods

Among the more important of Skel's CMS methods are getContentByAddress, getContentIndex, getParentOf, and getContentClasses.

The first three of these methods are fairly self-explanatory. The last one, getContentClasses, is a factory method that deserves a bit of explanation. Because an ORM translates data between static storage and program objects, we need to tell it which classes represent which data. getContentClasses is a way to do that while allowing future programmers to optionally change and expand the mapping. This allows you to replace, for example, \Skel\Page with your own implementation of the Page interface. It also allows other classes to query the CMS for available mappings.

Interacts With....

Like Db, Cms is used almost exclusively with App. Of course, it interacts circumstantially with a number of other interfaces (like Content and its derivatives), but its main job is to provide content to a blog-type application. More specifically, it is intended to be used with applications that show entire pages of content with very little other functionality. This is in contrast to user interface content (like buttons, menus, etc.), since these strings are intended to come from Db instead.

Related Minor Interfaces

As mentioned above, Content and its derivatives Page and Post are the primary "minor" interfaces associated with Cms. These interfaces simply define aspects of the content served by the CMS.

Examples

Again, the one primary example is in the case of a blog:

<?php
namespace MyBrand;

class App extends \Skel\App {
  protected $config;
  protected $cms;
  protected $db;
  protected $router;

  public function __construct(
    \Skel\Interfaces\AppConfig $config,
    \Skel\Interfaces\Router $router,
    \Skel\Interfaces\Db $db,
    \Skel\Interfaces\Cms $cms) {

    $this->config = $config;
    $this->router = $router;
    $this->db = $db;
    $this->cms = $cms;
  }

  public function getPage(string $url) {
    $content = $this->cms->getContentByAddress($url);
    if (!$content) return $this->getError(404);
    else {
      ....
    }
  }
}

The above code uses the app's dedicated CMS object to attempt to retrieve the content for the given url. If it doesn't find any content for the url, it returns an error.


Summary

While there are certainly other interfaces contained in the library, their roles should be more or less obvious given the above explanations. In general, the pieces of the puzzle fit together around the App, with Db, Cms, and Orm forming optional data layers for specific types of data. Router and Route provide the links between requests and functionality, and Component provides the means for transporting data within the app and for displaying it when it is time to do so. Finally, Config provides the single means for specifying mutable configurations for the various components of an application.


Table of Contents

Chapter 1: Conceptual Overview
  • What is Skel?
  • For whom was Skel built?
  • Why was Skel built? What problems does it attempt to solve?
  • How does Skel fit into the world?
Chapter 2: Skel's Pieces—A Bird's Eye View
  • Overview of current interfaces
  • How components fit together
Chapter 3: A Sample Application
  • Official Implementations
  • Installation and Setup
  • Routing
  • The data layer
  • Libraries
  • Internationalization
  • Packaging and Publishing
Chapter 4: Theory, In-Depth
  • Interfaces vs Classes
  • More about the data layer
  • The Component Interface
Chapter 5: Going Further
  • Skel roadmap
  • Extending Skel
  • Contributing to Skel
  • Future possibilities
Appendix A: The API Docs
  • How to Use the API Docs
  • Skel Header Package
  • Skel Uri Package
  • Skel Config Package
  • Skel Component Package
  • Skel Db Package
  • Skel Cms Package
  • Skel App Package
  • Skel Routing Package
  • Skel Http Foundation Package
Appendix B: Best Practices
  • Libraries vs Applications
  • The Component Interface
  • Dealing with Data
  • Interface, Interface, Interface