First things first - you'll need to grab the latest copy of the codebase. Follow the quick install instructions to clone the framework & the library.
When developing a site you'll want to ensure you're in either build or test mode, both of which will enable developer friendly error messages and debug-level logging. The recommended way of doing this is to set up an Apache Virtualhost to house your newly created project.
<VirtualHost *:80> ServerName jaoss-website.build ServerAlias jaoss-website.test SetEnvIf Host jaoss-website.build PROJECT_MODE=build SetEnvIf Host jaoss-website.test PROJECT_MODE=test DocumentRoot /var/www/jaoss-website/public <Directory /var/www/jaoss-website/public> DirectoryIndex index.php AllowOverride All Order allow,deny Allow from all </Directory> </VirtualHost>
If you're using made up domains as in the example above, don't forget to
add them to your
/etc/hosts file and make sure the IP points
to your webserver (127.0.0.1 if you're working locally).
If you can't be bothered to set up a VirtualHost, or you really want to run
your project as a subfolder (say, on a Wordpress installation), then you'll have
to add a
SetEnv directive to the root folder's .htaccess file (not
the one in the public folder). Just
make sure you remove it when you deploy to a demo / live environment - the default
mode if no environment variable is defined is live:
# this will only ever kick in if the preferred VirtualHost set up hasn't been followed # and the codebase is just being accessed directly in a subfolder. It's here as backup. RewriteEngine On RewriteRule ^(.*)$ public/$1 [L] SetEnv PROJECT_MODE build
Note that this approach has some big drawbacks in that the .htaccess file is version controlled, and it does now allow simultaneously running multiple modes. Unless you need the project to be a subfolder, the VirtualHost route is strongly recommended.
Your web user will need to be able to write to log/ and tmp/ (both relative to the project root).
If you're set up in build mode when you first run your project with any luck you should get some nice
developer friendly errors if either directory can't be written to, but if not then try making them
writable and trying again. Once these are set up be sure to keep an eye on the logs in the log directory - it's
recommended to always have a terminal open running
tail -f log/debug.log.
On its own,
PROJECT_MODE doesn't really mean a lot. Its core purpose is to control which
of the settings/*.ini files are loaded. Settings are loaded in the order noted above up to and including
the ini file matching the current mode. Therefore if your
PROJECT_MODE is set to
the framework will attempt to load (in order):
Consequently, if your
test, settings/test.ini would be loaded as well, whereas
if the mode was
live then only settings/live.ini would be loaded.
As noted earlier, settings cascade down through each mode such that those declared in the last loaded ini file will take precedence over any which have previously been loaded. This allows the bulk of the settings to be declared in settings/live.ini and refined at each level as required.
Once you've got a project set up you're ready to dive in - and where better than creating an app of your own? Before we do this, let's define exactly what we mean by an 'app' in the context of a jaoss project.
An app is a collection of one or more controllers, views, and / or models relating to one subject.
The term subject covers quite a broad range - suitable examples would be a 'blog' app or a 'users' app. That said, apps deliberately do not railroad the developer down any particular path, so ultimately an app is whatever you choose it to be - your entire project could be one app - it would just be rather unweildy and you'd struggle to re-use many of its components in other projects.
All of this leans towards treating apps almost as you would folders - the only restriction is that app folders should always be lowercase. Let's get on with it and create our first app folder and setting up some simple routing - we're going to make a very basic 'static' app which will simply render some very basic views for known URLs. Let us begin:
Don't worry, it gets a little more involving than this...
First things first - routes are referred to as paths in the codebase. The terms are interchangeable, but we'll stick to paths in this tutorial to keep inline with the classes and methods you'll encounter when working with jaoss.
One of the first things
jaoss will do when processing a request is try to work out what apps to load, and what paths
to load for each app. It does this by looking for a paths.php
file in each subfolder in the
apps/ directory. If it doesn't find this file it'll carry on regardless, though it will
log to log/verbose.log if you've got verbose
logging enabled (you won't, yet).
paths.php and add a single path to it. Add the following lines to
This snippet adds a path which will match a URL of /about (the first argument) to a controller action of the same name (the second argument). It takes additional optional arguments such as the controller to use and its location, which if omitted are worked out automagically based on the location of the paths.php file which was loaded.
There are two main methods available for loading paths, though loadPaths as used above is the most flexible and most consice (particularly when adding a large number of paths).
The Path Manager object is responsible for loading all available paths for each app as well as matching a request URL (e.g. /about) to a path. It also keeps track of which paths it's already tried to match - in short, it's a crucial part of a jaoss request.
Enough talk - head on over to http://<your-website>.build/about and see what happens. With any luck, you'll get a developer friendly error which says something like:
Could not find controller class 'StaticController' Controller class StaticController could not be found. The path searched was /var/www/yoursite/apps/static/controllers/static.php.
Go ahead and create the file it's after in
apps/static/controllers/static.php. Note that the filename
is a lowercased version of
the controller name, minus the 'controller' part since this is assumed from the fact the
file is inside a 'controllers' directory. Populate your newly created file as follows:
Upon refreshing the page you should now be presented with a Template Not Found error which gives you a clear indication as to what jaoss was expecting to find. If your controller action does not explicitly return anything, jaoss will attempt to render a template matching the name of the action it just executed (in our case, about.tpl). Let's create that file now - the error should tell you where it was looking for it, the first of which should be /var/www/yoursite/apps/static/views, so we'll put it in there.
Jaoss uses the Smarty template engine to render views. There are no plans to support other
mechanisms (such as Twig) just yet, though of course any forks are welcome to add this. Create
apps/static/views/about.tpl and add some basic content to it:
Refresh the page and you should see the fruits of your labour, with the <h1> sporting the content you assigned from the controller.
These three simple steps of creating a path, an action and a view don't seem like much but they pave the way for incredibly powerful, dynamic and flexible applications - and along with models form the backbone of any application you're likely to create.
Models are, as in any MVC framework, a fundamental part of most applications you'll make. This isn't the place to discuss what models are conceptually - but it is the place to discuss their implementation in jaoss, so let's do that. Each model you'll create is split into two distinct classes:
An object — a single entity — knows how to do things relating to itself, but not its peers. For example, an object might declare a method
getDisplayName() which might try and return a friendly name for a person (e.g. Nick Payne, or Nick, or @makeusabrew if I hadn't entered my full details).
It would not, however, declare a method called
findByName($forename, $surname) - this should instead be left to the model's
A table represents the concept of a collection of objects. As such, the vast majority of methods you'll declare in your table
will be similar to
getBy...() (no particular naming convention is enforced) which will in
most cases return an array of Objects. Principally, table methods deal with
reading either single or multiple objects.
One area where you may find jaoss differs to other frameworks is the fairly tight coupling between models and the data storage underpinning them - the only currently supported mapping being to a MySQL database. In general, tight coupling is bad, but jaoss is a framework driven by necessity, and thus far decoupling models from their database backend has not been necessary. Additionally, CRUD functionality is not isolated to one class or the other, hence they are both database aware. This behaviour is discussed above and summarised in the following table:
|Read||Table||read, find, findAll + your own|
This might lead you to think that the responsibilities are somewhat skewed and that the Table class is rather superflous - but it has one key property which is the corner stone of all models: the $meta array. It's this array which holds the definition for the fields (or columns) which represent and define your model's data.
You define your model's fields as a nested array inside your table's
$meta array. Arguably, these fields
could be declared as a property of the object instead - after all, it's objects which actually have
fields with values populated from database tables. However, therein lies the point: if you've got an object
then you want data, not data about data.
Similarly it is the (database) table which physically defines the fields we can store, so it is the (model) Table
which defines them in our application. Lastly, if you have an array of hundreds or thousands of objects, the
last thing you need is a useless array of meta data duplicated across each and every one.
Fields should never be declared directly inside the meta array - instead they should be declared in
columns array to allow for other meta data to be added in future*.
* this is pretty much the only example of 'we might need this...' you'll find in the framework. Everything else is there because it is used.
A list of available column types and other keys is discussed later - the important aspect for now is to remember
that fields must be declared within a
We could discuss the concepts of models in a lot more detail, but let's just get stuck in with a real world example. Let's create a more advanced app which will deal with models and more advanced routing.
The app we just created was pretty boring, and not exactly very dynamic. Let's look at creating the staple of many websites; some news. To do this, we're going to want a model to represent our news stories, and some more dynamic routes to cater for the fact our data is also dynamic. Our news is going to be user generated - anyone can anonymously submit news providing they enter an email address*. We're going to need:
*obviously, in a real application this would lead to complete carnage without some form of user authentication or article moderation - topics which are covered later.
Let's dive straight in by creating our model to represent our articles:
Let's create the MySQL table to store our model. We're going to use the CLI Tools to do this,
though of course we could just create it manually. Notice in the snippet below we manually pass the
environment variable as by default the CLI tools work in test mode, whereas we want to work in build mode for now. If you don't
want to have to type
each time you invoke the cli tools you can of course run
export PROJECT_MODE=build on a bash like shell.
PROJECT_MODE=build ./jcli create table ArticlesPROJECT_MODE set to build Looking for model in project apps directory... Found Articles model! [PDOException] SQLSTATE  Access denied for user 'youruser'@'localhost' (using password: YES)
Ah. That's not quite what we wanted to see. We haven't given jaoss our access credentials to our database yet so it's using
the defaults found in settings/live.ini which
are of course incorrect. Rather than alter them directly, we're going to override the relevant settings in
since that's the mode we're working in. Create a database for your project if you haven't already (I'm using
demosite_build) and then update
Let's run the
create table command again. You should see something like:
PROJECT_MODE=build ./jcli create table ArticlesPROJECT_MODE set to build Looking for model in project apps directory... Found Articles model! Table articles created on database [demosite_build] Don't forget to add this new table to any test fixtures! Done (0.082 secs)
Perfect - we've now defined our basic model and created a database table to store it in. Let's dive in and create a path which will allow users to add articles to the site. While we're at it, we'll also create a placeholder news controller and a placeholder view containing a form so a user can add an article:
If you load http://<your-website>.build/articles/add in your browser you should see your simple - and completely empty -
form. Notice that you can submit the form and the page will still render even though the request method of the form is set to
by default, paths respond to any request method, though you can restrict this.
Let's turn our attention to that form, because without it we can't get very far. The framework ships with a useful helper in its default application called field.tpl, which although not pretty to look at, is incredibly useful when dealing with forms and in particular forms which need to be generated based on a model's fields. Let's refactor our view it in incremental stages to shed a bit of light on how the field helper works. Firstly, let's add some fields in manually:
Refresh the page - you'll see a list of labels and text inputs corresponding to the field names you passed through when including each field template. Naturally, the label names and the field types aren't quite right, so let's sort that out:
Which should end up looking something like:
We're getting there - but this probably all feels a little bit like déjà vu because we're pretty much
just re-declaring the contents of our table's
columns array. In fact, the first thing the field
helper does is look for a smarty variable
of that name, which if it finds it then uses to render the appropriate label, input type and attributes for the given
field. Let's make that information available to smarty and refactor our view:
Notice that our view is now much simpler again - all we have to do is tell the field helper which field we're interesting in rendering, and it does the rest. The result looks like this:
Perfect - we've even got the proper label names too. We're now ready to beef up our action to handle the post request, validate the user's input, and if it is valid then create a new article. If we do create a new article, we'll redirect the user straight to view it. If we don't, we'll render the form again and notify the user of any errors with their input. This entire flow is shown in the snippet below:
As you can see, without the over-commenting and unnecessary temporary variables (shown for clarity), the actual logic involved in this common flow is minimal.
Go ahead and fill out the form with some valid data and hit the submit button. Strangely, if everything
worked you'll be greeted with a developer error, but fear not - this is only because we're trying to
redirect to an action which doesn't exist yet (
view_article). As a brief aside, let's take a look at
the output in debug.log for a POST request to our add_article handler:
06/10/2011 11:22:13 (DEBUG) - matched pattern [^/articles/add$] against URL [/articles/add] (location [apps/news] controller [News] 06/10/2011 11:22:13 (DEBUG) - Init [NewsController->add_article] 06/10/2011 11:22:13 (DEBUG) - Start [NewsController->add_article] 06/10/2011 11:22:13 (DEBUG) - Validate::required(test) [title] - [OK] 06/10/2011 11:22:13 (DEBUG) - Validate::required(this is my test article) [content] - [OK] 06/10/2011 11:22:13 (DEBUG) - Validate::required(email@example.com) [author_email] - [OK] 06/10/2011 11:22:13 (DEBUG) - Validate::email(firstname.lastname@example.org) [author_email] - [OK] 06/10/2011 11:22:13 (DEBUG) - Article::setValues() returning [true] with error count  06/10/2011 11:22:13 (WARN) - Handling error of type [CoreException] with message [No Path found for options] and code 
So - our trusty users can now add articles to our website, but they can't yet see them. Let's tackle that next.
So, we need to create a path which will handle requests to view an actual article. Unlike those we've looked at thus far, this path is going to have to be able to accept a dynamic parameter - in this case, the ID of the article we wish to view. We do this using a combination of regular expressions and named subpatterns. You don't have to know a massive amount about either - in fact, the routes we've been entering prior to this point have been simple regular expressions all along - you just didn't know it. Let's add our new path now:
All we're doing is capturing a named sub pattern called
id which, if matched, will always be an integer as denoted
\d+. Any sub patterns captured in your paths are available in your controller via the
getMatch method. Let's create our new action
and view to render our articles - notice that we've stripped out the comments and unnecessary variables from
save a bit of space:
Throughout this tutorial, many topics will link directly to the relevant class / folder / template relating to the discussion. These links are worth taking a look at in context to get a better understanding of what exactly the tutorial is describing. They will be shown like this.