Home  Support Page

A Beginners’ Guide

Contents

Introduction

First of all, welcome to Phoebius framework. We see Phoebius as something that developers needed to produce complex web-services natively featured everything that well-known gurus of software development advised to all of us: principles of cohesion and decoupling by Steve McConnell, patterns by Martin Fowler, and so on and so forth. All this makes a resulting product more stable and less obscured, thus minimizing costs of development, test and maintenance and delivering the pleasure to developers, project managers and customers.

Phoebius framework is the best choice for the basis of long-run web applications that meant to be scalable enough to meet the requirements of extremely-changing real world.

In this tutorial we will develop a classic weblog, introducing you almost every package of the framework and how it makes the life of the developer easier.

Preparing the Environment

After downloading and application stub and extracting the Phoebius framework source to the corresponding directory, consider that /www is mapped as web-server root and /tmp has enough permissions for writing.

Inside this tutorial we consider that an absolute path to our application is $app (and it is mapped to the root of an application stub), and a path to the framework core is $base (that points to the /phoebius directory). Of course, you can locate them in any relation: $app can be inside $base and $base can be inside $app; the only requirement is the correct absolute path to the init script (that is located at $base/etc/app.init.php).

A typical application has the following structure:

/ (aka $app)
	/cfg
		/default
			/config.php
	/etc
		/config.php
	/lib
	/phoebuis (aka $base)
	/tmp
	/var
	/views
	/www
		/index.php

The bootstrapping process of such application is the following:

  1. the incoming request is routed by a web-server to a file called front-controller (it is $app/www/index.php by default, but this script can be located anywhere you like - just assume that you have specified the correct absolute paths to an init-script);
  2. front-controller loads the framework's init-script $base/etc/app.init.php;
  3. front-controller loads the application config $app/etc/config.php. An application config prepares everything that does not depend on an environment where the application runs, e.g. a custom path to a library classes (by default it is $app/lib:$app/var/lib);
  4. front-controller loads the host config (located at $app/cfg/<slot name>/config.php). A host config can differ over different environments (e.g. test, production, development, etc), therefore it defines a host preset (the verbosity of errors, and more), stores a database connection parameters, tunes a web-server and does much more. The host configs are aggregated into the application slots. The name of an application slot is determined from an environment variable called PHOEBIUS_APP_SLOT. If not defined, the "default" slot is used.

For our simple blog application, all we need is a single database connection, so our application config can be left untouched, and the default host config ($app/cfg/default/config.php) can look similar to this:

<?

	define ('APP_SLOT_CONFIGURATION', SLOT_PRESET_DEVELOPMENT);

	DBPool::add(
		'default', 
		PgSqlDB::create()
			->setHost('localhost')
			->setUser('postgres')
			->setPassword('postgres')
			->setName('blog_db')
	);

?>
Here we have specified a development preset of the host (which maximizes the application feedback by an error verbosity an detailed logging) and have defined a default PostgreSQL database (you can specify MySQL swimmingly because the Phoebius database abstraction layer uses the ANSI-SQL-unified query builder, so you don't need to write SQL queries manually).

Defining a Project Domain

A development of an application starts with describing the domain with an XML schema, which is then translated to an internal representation (i.e. to a graph of internal objects), called "ORM domain". ORM domain holds the information regarding the application entities, mappings between entities and database schemes, and much more.

So let's define the essences of our application. Our simple weblog is represented by posts and comments. A Post contains the title, the text, the date, and the set of Comments left to the post. A Comment contains the author, the text and the post this comment is assigned to, and the time when the comment was published. In other words, we have a one-to-many relation. Now let our framework know about our domain. Firstly, write down the definition in a simple XML schema located at $app/var/schema.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE domain SYSTEM "../phoebius/share/Orm/Domain/Meta/Xml/abstract.dtd">

<domain db-schema="default">
	<entities>
		<entity name="Post">
			<properties>
				<identifier />
				<property name="title" type="String" />
				<property name="text" type="String" />
				<property name="date" type="Date" />
				
				<container name="comments" type="Comment" />
			</properties>
		</entity>
		<entity name="Comment">
			<properties>
				<identifier />
				<property name="author" type="String" />
				<property name="text" type="String" />
				<property name="post" type="Post" />
				<property name="time" type="Timestamp" />
			</properties>
		</entity>
	</entities>
</domain>
For more information, refer to /share/Orm/Domain/Meta/Xml/abstract.dtd.

Then we tell the framework to produce the code according to the definition, by calling the framework generator via the console and passing the absolute path to the directory where the application resides. The real-world example:

	
$ php /www/phoebius.org/phoebius/bin/make.php --code --schema --db=default --config-file=default var/schema.xml

Now you have a fresh set of autogenerated classes and files, that represent a high-level abstraction over database schemas and framework internals. The most important are:

  • $app/lib/Domain/Post.class.php, the class representing the Post entity;
  • $app/lib/Domain/Comment.class.php, the class representing the Comment entity;
  • $app/var/db/default.sql, a SQL schema produced for the RDBMS driver you have chosen in a host config and already queried against it. Push it to the database you use.

Routing the requests

Phoebius has a powerful routing package, which can route requests even using the complex conditions and mapping variables to types. Consider, that our blog requires to handle the following types of requests:

  • / -- the front page
  • /?skip=<skip_count> -- the list of previous posts starting with skip_count, where skip_count is unsigned integer
  • /post/<post_id> -- a separate page containing post (identified by the post_id) with associated comments

To route these requests we need to create a router. The simplest way to do that is to create a class that implements a framework's ChainedRouter. Let's name it BlogRouter and locate it at our application library:

$app/lib/BlogRouter.class.php

class BlogRouter extends ChainedRouter
{
	function __construct()
	{
		$this->setDefaultDispatcher(new MvcDispatcher());
		
		$this->fillRoutes();
	}
	
	protected function fillRoutes()
	{
		$this->route(
			'skip-posts',
			'/?skip',
			array('controller' => 'Blog', 'action' => 'skipPost') 
		);
		
		$this->route(
			'show-post',
			'/post/:id',
			array('controller' => 'Blog', 'action' => 'showPost')
		);
		
		$this->route(
			'front-page',
			'/',
			array('controller' => 'Blog', 'action' => 'frontPage')
		);
	}
}
					

As we are planning to use Mvc to handle the requests, we set an MvcDispatcher as the default route dispatcher. Dispatcher is an application layer that is responsible for passing the request to the corresponding handler.

ChainerRouter uses natural prioritizing strategy: the firstly-added rule has higher priority than the last one. That's why the route for front page is defined at the end of the table as it uses less greedy set of rules.

Handling requests with actions

In Mvc context, an action is a procedure that is responsible for handling the request and producing the result. Usually, action is a method of the controller class. Our routing policy defined below specifies requests that should be dispatched by MvcDispatcher and handled by the "Blog" controller. Therefore we define a class BlogController with the following actions (consider the naming notation defined by the base ActionBasedController class):

$app/lib/Mvc/Controllers/BlogController.class.php

class BlogController extends ActionBasedController
{
	/**
	* Request: /
	* @return IActionResult
	*/
	function action_frontPage()
	{
	}
	
	/**
	* Request: /?skip=N
	* @return IActionResult
	*/
	function action_skipPosts(Integer $skip)
	{
	}
	
	/**
	* Request: /post/:id
	* @return IActionResult
	*/
	function action_showPost(Integer $id)
	{
	}
}
				

Each action handles the corresponding request, if matched. Noteworthy that end-developers should not care about incoming parameter types: the framework internals checks the compatibility of the request by itself. For example, the request /?skip=20 will be passed to BlogController::action_skipPosts() but /?skip=abc won't. Integer here is the defined type. You can also use types defined in a project domain: Phoebius will use incoming variable value as the identifier to fetch the object.

Applying a business logic

Now it is time to fill our actions with business logic. We will do this for every action that handles the request.

The front page is simply a page with the latest posts. Therefore we can just make an internal redirection to an action that handles skip-posts request skipping zero posts:

function action_frontPage()
{
	return $this->action_skipPosts(new Integer(0));
}
Action, that fetches the list of Post object, uses simple criteria over the entity query language wrapper -- an EntityQuery class, and puts this list and the number of skipped posts to a model (a transport between action and view) and return the name of the view, that is automatically resolved by the action invoker. Notice, that we do not mix action's business logic with any presentation logic: we pass the result of business logic away to a view, which itself is responsible for presentation:
function action_skipPosts(Integer $skip)
{
	$list = 
		Post::query()
			->setOffset($skip->getValue())
			->setLimit(25)
			->getList();
	);
	
	return $this->view(
		'posts',
		array(
			'posts' => $list,
			'skip' => (string) $skip
		)
	);
}						

An action for the page with a separate Post is simple too. We access the Post object by the specified ID and then obtain all the comments for the Post using the EntityQuery criteria:

function action_showPost(Integer $id)
{
	$post = Post::dao()->getEntityById((string) $id);
	$comments = 
		Comment::query()
			->where(Expression::eq('post', (string) $id))
			->getList();
			
	return $this->view(
		'post',
		array(
			'post' => $post,
			'comments' => $comments
		)
	);
}						
Note that we reference to the name of the Comment object property ("post"), no matter how the database column is named.

Processing a resulting content

We have created actions that only apply a business logic, but don't care about the presentation, because in modern three-tier applications we should divide the business and the presentation logic. The result of an action (a model, that itself represents a produced data) is passed to a view. A view is a plain script that produces the markup and injects the passed data within this markup.

A Phoebius UI package is a view engine that offers a wide range of features for easy management of a huge number of different views. It defines three different types of views:

  1. Master page view
  2. Content page view
  3. Control view
Using these views you can create complex view hierarchies, make view result substitutions, and even support a compliance between the script and a class written exclusively for the view or set of views (a "code behind" feature, introduced by ASP.NET, so that the template helpers are introduced within the classes and nowhere else). Master page view defines a skeleton of a unified website page (e.g. the header, the footer, the menu and the place for page content). Content page view itself represents a a separate page, and can be used as standalone template or as a part of master page. Control view works similar to a content page view except that control can have a parent view thus inheriting the model.

For our simple weblog we need to create a common master page, three content pages (the first is for the list of posts; the second is for the select post with comments left to it, and the third one is 404 page) and one control page for composing a menu.

$app/views/master.view.php


...static header...

<?

$this->renderPartial('menu');

$this->getDefaultContent();

?>
...static footer...

$app/views/posts.view.php

<?

$this->setMaster('master');

?>
<h1>Blog posts</h1>
<?

foreach ($this->posts as $post) { ?>
    <h1><?=$post->getTitle()?> <small><?=$post->getDate()->toFormattedString()?></small></h1>
    <br />
    <?=$post->getText()?>
    <hr />
<? }

?>							

$app/views/post.view.php

<?

$this->setMaster('master');

?>
<h1><?=$this->post->getTitle()</h1>
<?=$this->post->getText()?>							

Standard Phoebius distribution requires views to be located inside $app/views directory. The naming pattern is <view_name>.view.php.

Conclusion

Phoebius framework is modern solution intended to quick building of applications that require self-documenting stratified code of high quality.

More information

Download the AJAX application example or read the manual pages.