Architecture Guideline - How to structure your classes.
TODO: this page needs a better organisation, and a better explanation of its purpose. The primary reason for creating it was to get this stuff out of the way in LearnProgramming and ProgrammingGuideline.
See also:
- LearnProgramming - useful resources to get started.
- ProgrammingGuideline - how to format your code, and how to name your identifiers.
General Guidelines for Classes, Methods etc
Small classes, and small methods! Two objects talking to each other are better than one object doing unrelated things. Our code fails this guideline almost everywhere, but especially in the MyTB part.
However: everything in moderation! Don't create 1,000,000 classes each consisting of one line of code. Splitting a class in two halves in two tightly connected parts is as bad as having one big class that does too much. Aim for 'whole' objects: objects that do all the things that they conceptually should.
Don't let your inheritance hierarchies grow too big. Composition is often a better solution than inheritance. Currently our page classes have a quite deep inheritance hierarchy.. we hopefully find a way to split this up.
Limit the visibility (scope) of variables, objects, services!
- Passing things around (objects, values, services) is better than making them globally available. For instance, having the request (POST, GET, URL) passed as a method argument is better than retrieving it from a global-scope or static function.
- method arguments, return values and local method variables are your friends! Don't read and write on member variables all over the place, if method scope would be totally sufficient.
- Give information only to those components which will or could need it. This question becomes easier, if you define a clear role for each component.
- Write components which are happy with a minimum of information, agnostic about the outside world (thx Peter for the idea of saying 'agnostic').
- If methods A and B work with different sets of data, then consider to put them into different classes! But ONLY if they really are very different.
- The ideal component does only use values and objects injected by setters or in the constructor, and does only call methods on these injected objects.
- However, ideals are castles in the sky. Aim for things that work and are flexible. Your rule of thumb shall be: write code that, when you come to it in six months and have to extend it, doesn't make you cry.
- If there are too many objects to pass to one component, consider to stuff them into a container altogether.
- Global scope is totally out and yesterday.
Class names are globals!
- It's good if your component can be agnostic about the outside world, especially global symbols like class names and floating functions. It's better to have everything being given from outside via setters ("dependency injection").
- It's even better to call an init method on objects and passing values. Setters mean knowledge of the inside of objects, that's been stated already.
- Using a method of a local object (method scope or object scope, maybe given as a parameter from outside) is better than calling a static function or singleton function.
- Wrong. However, it is better that you aim to CREATE local objects rather than singletons or static functions.
- Reduce the places in your code where you call an explicit constructor. If the same class is constructed in 20 different components, you should consider to write a factory, and give the factory to those components which want to create an instance.
- OR create one object instance in toplevel, and pass that around instead of the factory.
- But please don't do this if the object is to maintain some sort of state.
- Don't use too much inheritance, as this does again use explicit class names. Use composition instead. Inheritance is ok, but don't put too much stuff in the shared parent class - instead, let the parent class use some services given from outside to do the job. For instance, a parent class for view objects should not do the translations itself, but instead take a translation engine object as a parameter. This way, you can more easily take the parent class to another project, if you want to reuse the component.
Try to separate the MVC layers of model, view and controller.
- controllers look at the user input (request), and decide which page should be shown, with which data.
- view classes (pages, widgets, or however you split it up) call templates, and decide which layout elements go where on the page.
- template files should contain most of the html markup, and ideally not too much php.
- model classes provide a connection to the database.
Organisation of Code
MVC Applications
Applications are the components which define our pages, how they are mapped to a http request, and how they interact with our database tables. Each application has its own folder in build/$appname. See [RequestRouting] and [InversionOfControl] for more information.
When coding with MVC (Model-View-Controller), we separate this into three layers.
The controller decides how to deal with user input. In a web application on server side, all user input comes as http requests, eventually with POST or GET arguments. Some additional info is in the session (is the user logged in, etc). In our architecture, each application has a class SomethingController stored in build/something/something.ctrl.php, with a method SomethingController::index($args = false) and eventually some other methods. The index() method is supposed to return a $page element which provides a $page->render(); method (this method will be called in the framework).
The view layer contains all the layout markup, and also how layout from one page is reused on another page. As with most web projects, the XHTML layout is preferably stored in template files (see below). A specialty of our project is that the layout reuse is expressed in Page classes deriving from each other?. You can see this at work in the build/about application.
The model layer contains methods to retrieve stuff from the database, or to write on it. It is a good idea to split this code in more than one class, to reduce class size and separate responsibilities.
Models can derive from
- RoxModelBase if they behave more like a gateway or function library, or
- RoxEntityBase, if they represent a specific entity in our universe (like a member, a group, a message, etc).
See Cohesion (wikipedia), especially "Communicational cohesion".
Template Files for HTML
Whether using a template engine (Smarty) or not: HTML code should go into template files.
How does a template file look like?
- PHP templates begin with <?php ?> or <?php ... ?>, where ... can be some code to prepare local variables. (better to have this preparation code somewhere else!)
Where can you store template files?
- Traditionally, templates are stored somewhere in "templates" or "templates/shared".
- Templates $filename.php for an application $appname are traditionally stored in "templates/apps/$appname/$filename.php". The problem with that is that you have to jump between the application build folder "build/$appname" and the template folder all the time, which can be a pain.
- After a recent discussion on the mailinglist, it can be considered acceptable to stored templates in the application folder, such as "build/$appname/templates/$filename.php". This allows an easier navigation in the filesystem.
How to include a template file?
- Simplest way is to write include "$path/$filename";. For the $path, you can use constants like SCRIPT_BASE or TEMPLATE_DIR. The rest will often be just explicit path strings with slashes. We don't use require_once, because we want to show the template no matter how many times it has been shown before.
- In the bwrox framework you find some attempts to create an "advanced" mechanism for including templates, which are meant to be more flexible and allow a template switcher, which would allow to override certain templates or switch between "themes". These mechanics are not really finished, so better stick to the traditional way.
- Most templates need some variables to work with, such as $username, $message_text or things like that. A template's scope is the same as the method it is included with, so you should declare all these variables in method scope, as shown in the example below.
- Templates included in methods will naturally be able to use the $this keyword to refer to the method's object.
The calling context
class IWasHerePage
{
function graffity()
{
$who = "Nepomuk";
include SCRIPT_BASE."build/iwashere/templates/iwashere.php";
}
}
The template file:
<?php ?> <p><?=$who ?> was here.</p>
International Text
As our website should be translated in many languages, we should avoid printing any hardcoded english text. Instead, we use word codes to be looked up in the database (hopefully soon with a decent caching system). See WordsModule to see how this works.
Filenames and Filesystem Structure
Filenames
- All filenames should be written in lower case (<tt>showmembers.php</tt>). Don't use _underscores_.
- If a file defines a class with a specific role which is frequently used throughout the project, the role can be added to the filename, like this
- SearchmembersController goes into searchmembers.ctrl.php
- AboutFaqPage goes into faq.page.php
- ItemlistWidget goes into itemlist.widget.php
Filesystem directories
- Applications go into build/$appname
- Template files go into templates/$appname or into build/$appname/templates
Database
You can find some info on DB design in the ProgrammingGuideline.
Anyway, if you want to create new tables or columns, you should discuss that in the team first!


