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 Component
s, 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
Component
s' 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 Db
s and Cms
es 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 Component
s. It has methods like filter
and contains
that allow you to query the Component
s 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 Component
s, 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 App
s (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 Route
s, 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
, aRouter
, aDb
, and aCms
. - 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, theCmsConfig
interface, an exception will be thrown when you try to hand theCms
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'sprepareUiForError
method is run. The app will also run some preparatory code when the finalComponent
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 Component
s, 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