Horus Framework 7

" Faster, Optimized Web Apps "


Day 1 "Introduction"

Day 2 "Start"

Day 3 "Advanced"

Introduction

The Horus Framework is a pretty solid attempt at designing a good OOP framework in as a small package as possible. Focused on a light codebase and on a solid, standards-friendly approach, Horus can be used to write advanced applications without having to deal with complex coding toolkits like Zend or CakePHP. Horus is easy to learn and the code is pretty well commented in case developers need help figuring out what's going on. -- Softpedia


Features

  • Just a one file ~ 30 kb .
  • Full featured .
  • Very light .
  • Really Zero configurations .
  • Subdomain routing .
  • Built in full flexible router .
  • Simple and extended PDO based database manager .
  • Simple container Registery pattern .
  • Accepts JSON/XML as input formats for any REQUEST_METHOD
  • Any request var of any request method is now in _REQUEST array
  • Very powerful with for web services RESTful, XMLRPC, ... etc
  • Full output control .
  • Clean code .

Download Horus

Setup Horus

  • After downloading horus. unpack it
  • You will notice that it's about three files {Horus.php, index.php, .htaccess}
  • The main file is just Horus.php
  • index.php is just a demo
  • .htaccess is for mod_rewrite Horus can work without .htaccess
  • There is Zero configurations needed to use horus
  • Now open index.php and see the demo

How horus really works ?

  • User enter horus based site in the browser e.g: www.site.com/index
  • PHP calling the handler of /index, PHP !!, instead of Router
  • If horus found it, it would run it's handler, show it to the browser then Horus exit
  • Else if it didn't find it, it will show 404 page then exit

Quick test

In your index.php :

<?php
    
    // load it
    include 'Horus.php';

    // start it
    $horus = new Horus;

        // when the borwser enter the 'GET /' show it 'hi index';
        $horus->router->get('/', function(){
            echo 'hi index';
        });

    // run
    echo $horus;

Application Router


› What is a router ?

Router is a library that controls the application response to the browser .
The router is just like a mini-web server that serves a path with its handler and when the browser call it, will be distapched . But there are many routing mechanism, the most popular is storing each route you enter in an array then when the browser enter a certain path, start searching for the matched route, this is good, but only for small apps imagine if you have many and many routes and you need the quickest and shortest response time !, you know that in big applications you need scalability and small response time even microsecond is considered valuable !, so the new verion of Horus Router is using another one called Call on demand and no routes storage in memory, How ? .
Say that you told horus to serve some routes, say 5 routes, now when the browser request any valid one Horus will ignore any other route and just send the required response then exit, so that Horus 7 is more faster and nearly just like plain-php code in response time .

› Features :

  • Very simple .
  • Very Light and Really very fast !
  • Supports any valid PHP Callback .
  • Supports full class routing like controller .
  • Support filepath as a handler to be loaded.
  • Supports Sub domain Routing
  • Lazy on demand route dispatch
  • Uses No loop to dispatch the requested route
  • Dosen't store routes in array, so any number of routes won't affect performance

› Clean URLs :

By default if your server supports url_rewrite module just like mod_rewrite you will be able to use clan urls easily .
But what if your server won't let you to use it !, Don't worry horus is here .
Our Simulator will be instead server url_rewriter .
Don't worry it's very simple to setup, open horus .htaccess you will find setEnv SIMULATOR off this will only work if your server supports rewriting, but if not ? Horus will use index.php/ and PATH_INFO to do the trick .

› Usage :

<?php

    // --------------------------

    // now load and start horus easily
    include 'Horus.php';
    $app = new Horus;

        // Horus support any HTTP methods standard or even custom
        // here is we will respond to the browser when it call '/hi' using 'GET'
        $app->router->get('/hi', function(){
            echo 'HI';
        });

        // here is we will respond to the browser when it call '/hi' using 'POST'
        $app->router->post('/hi', function(){
            echo 'HI';
        });

        // here is we will respond to the browser when it call '/hi' using 'PUT'
        $app->router->PUT('/hi', function(){
            echo 'HI';
        });

        // here is we will respond to the browser when it call '/hi' using 'XYZ' 
        // xyz: is a custom method this means horus will respond to any 
        //      request method
        $app->router->xyz('/hi', function(){
            echo 'HI';
        });

        // here is we will respond to the browser when it call '/hi' 
        // using 'GET' or 'POST'
        $app->router->get_post('/hi', function(){
            echo 'HI';
        });

        // here is we will respond to the browser when it call '/hi' 
        // using 'GET', 'POST' or 'PUT'
        $app->router->get_post_put('/hi', function(){
            echo 'HI';
        });

        // here is we will respond to the browser when it call '/hi' 
        // under any http request method .
        $app->router->any('/hi', function(){
            echo 'HI';
        });

        // handle mutliple paths at once ?
        $app->router->any(array('hi', 'hi-2', 'hi.html'), function(){
            echo 'HI';
        });

        // handle a path and pass default arg(s)
        $app->router->any('hi', function($fname, $lname){
                echo "hi $fname $lname";
        }, array('Mohammed', 'Al Ashaal'));

        // handle group of routes based on a path ?
        $app->router->with('/x', function() use($app) {
            
            // '/x/page1'
            $app->router->any('/page1', function(){
                echo 'page1';
            });

            // '/x/page2'
            $app->router->any('/page2', function(){
                echo 'page2';
            });

            // '/x/page3'
            $app->router->any('/page2', function(){
                echo 'page3';
            });

        });

    // run it
    $app->run();

URL parameter :

<?php

    //load horus
    include 'Horus.php';

    // start horus
    $app = new Horus;

        // respond to '/page/@num' ex: '/page/1' .. etc
        $app->router->any('/page/@num', function($id){
            // when the browser enter e.g: '/page/5' or any othe than '5'
            // we will show it the number it requested
            echo $id;
        });

        // respond to '/page/@str' ex: '/page/test-123' .. etc
        $app->router->any('/page/@str', function($string){
            // when the browser enter e.g: '/page/test-123' 
            // we will show it the string it requested
            echo $string;
        });

        // respond to '/page/@num/@str' ex: '/page/2/test-123' .. etc
        $app->router->any('/page/@num/@str', function($number,$string){
            // when the browser enter e.g: '/page/3/test-123'
            // we will show it the number & string it requested
            echo 'The number is: ', $number,  ' ,But the string is: ', $string;
        });

        // what is @num and @str ?
        // horus is using regex and by default horus comes
        // with regex vars and @str/@num some of them
        // and those are the vars registered in horus router
        /*
            '@num'      =>  '([0-9\.,]+)',
            '@alpha'    =>  '([a-zA-Z]+)',
            '@alnum'    =>  '([a-zA-Z0-9\.\w]+)',
            '@str'      =>  '([a-zA-Z0-9-_\.\w]+)',
            '@any'      =>  '([^\/]+)',
            '@*'        =>  '?(.*)',
            '@date'     =>  '(([0-9]+)\/([0-9]{2,2}+)\/([0-9]{2,2}+))',
            '@null'     =>  '^',
            '@domain'   =>  $_SERVER['DEFAULKT_DOMAIN']
        */

        // set/override any regex var ?
        $app->router->vars['@page1'] = '(page1)';

    // run it
    $app->run();

Other Router features :

<?php

    //load horus
    include 'Horus.php';

    // start horus
    $app = new Horus;

        // you can load a file
        $app->router->any('/page', 'path/to/file.php');
        // note: if there were some url params, will 
        // be as array in a var called $args, just var_dump($args)
        // in the filename used like above


        // you can use classes
        // let we have this class
        // every method will be like a seprated page
        // every class must has 'index' method because 
        // it will the landed page
        // any path requested by browser that has '-' or '.'
        // will be replaced by '_' to be correct for method name
        // any method starts with '_' will be private/protected .
        // the controller class should extends Horus_Controller
        // that will provides you with simple and better tools .
        // Horus Controller is also extending Horus_Container .
        Class Page extends Horus_Controller
        {

            // instead of __construct
            // optional
            function __init()
            {
                // will automatically called in construction
            }

            // instead of __call
            // optional
            function __caller()
            {
                // to achive extensible controller
                // i'm using __call() to check if the
                // the requested method isset or not
                // this is good for dynamically added methods
            }

            // '/' or '/index'
            function index()
            {
                echo __FUNCTION__;
                $horus = &$this->horus; // get horus object.
            }

            // 'page-1' or 'page_1'
            function page_1()
            {
                echo __FUNCTION__;   
            }

            // private method [protected/private/starts with '_']
            function _page()
            {
                echo 'private and protected from the router';
            }
        }

        // to route this class, just put it's name or start and 
        // put it's object
        // 1). it's name
        $app->router->any('/page', 'Page');
        // 2). or
        $page = new Page;
        $page->newMethod = function(){echo 'the new method !!!'};
        $app->router->any('/page', $page);

        // Do you want to make wildcard regex routing ?
        $app->router->any('/page-1/@*', function($requested) use($app) {
            // $requested: will contains everything after '/page-1/'
            // if you want to check if current path matches what you want ?
            var_dump($app->router->is('page-1/test'));
        });

    // run it
    $app->run();

Sub domain routing :

<?php

    //load horus
    include 'Horus.php';

    // For sub domain routing, you must set the default main domain of your site
    // localtest.me : re-route any request to your 127.0.0.1 even subdomains !
    $_SERVER['DEFAULT_DOMAIN'] = 'localtest.me';

    // start horus
    $app = new Horus;

    // handel scheme://{any-string}.localtest.me/
    // you must write '//' in the start to tell horus that this is
    // a sub domain routing
    $app->router->any('//@str.@domain', function($sub){
        echo $sub;
    });

    // note: that this feature based on your server configurations:
    // > Virtual hosts .
    // > Wildcard DNS support .

    // run it
    $app->run();

Direct regex routing :

<?php

    //load horus
    include 'Horus.php';

    // start horus
    $app = new Horus;

	// Example url: http://localhost/123
	// Return: This is number 123
	$app->router->get('/([0-9]+)', function($number) {
		echo 'This is number ', $number;
	});
	
    // run it
    $app->run();

Database

As this micro-framework only needs to be a tiny base for other php based apps, so i just extended PDO class and merged it with PDOStatement and overrided the PDO::query() method to provides you with a very simple interface to deal with . so you just initialize it and start coding using Horus::sql



// include horus
include "Horus.php";

// start it
$app = Horus();

// start the sql
//:: $app->sql->connect($dns, [$username, $password, $options]);
$app->sql->connect('mysql:host=xx;dbname=yy', 'username', 'password');

// query
//:: $app->sql->query($statement, [$binded_params]);
$app->sql->query('select * from mytable') or die("cannot do select from mytable");

// get result [all]
var_dump($app->sql->fetchAll());

// how about prepared stmnt for security ?
$app->sql->query('select * from mytable where col1 = ? and col2 = ?', array('val1 for first ?', 'val2 for second ?')) or die("cannot do select from mytable");

// now fetching using while ?
while( $row = $app->sql->fetch(PDO::FETCH_ASSOC) )
{
    // your code here
}

// and so, use PDO, PDOStatement and Horus query() fron one class
// But wait !, how about new instance of the class ?
// just do this:
$newSQL = $app->sql->newInstance();
$newSQL->connect(/* .... */);
# $newSQL-> ....


// run it
print $app->run(); 

// or just this: 
print $app; // o.O

Helpers

› Constants

URL The full http url to the the base application directory
ROUTE The full routed url that may contains index.php/ or ?/
DS shortcut to DIRECTORY_SEPARATOR
BASEPATH Full path to the main folder of your application e.g:\
COREPATH Full path to the folder that contains Horus.php e.g:\core\
HORUS_START_TIME The microtime that horus started at it
HORUS_START_PEAK_MEM The peack memory when horus started
Horus_Version The Horus Version Number

› Environment vars $_SERVER "auto-detected"

DEFAULT_SCHEME string The scheme used in urls http, https ... etc
DEFAULT_DOMAIN string The default domain used in urls, by default it equals SERVER_NAME
SIMULATOR string on or off whether the simulator is working or not
SIMULATED bool true or false checks whether the simulator started or not
ROUTE string Full routed url of current request url with/without index.php/

› Helpers methods $app = new Horus;


# Horus() Get horus instance


# echo/print $app $app->run()


# Horus::instance() Get horus instance


# $app->input($needle = '', $default = false, $callable) Get from current request array and optionally filter it by callable


    // instead of $_GET & $_POST but just like $_REQUEST
    // but more , it reads 'php://input' too !
    // also it (may) work with json
    // now get a key from GET '?key1=val1&k2=v2'
    $app->input('key1');

    // get from POST key called 'post-key'
    $app->input('post-key');

    // also get from any request method
    // but what if the key not exists ?
    $app->input('nokey', false);
    // if the current request don't has key called 'nokey'
    // it will return false 
    
    // another example:
    $app->input('nokey', -1);
    // if the current request don't has key called 'nokey'
    // it will return -1

    // to get all inputs array
    var_dump($app->input());

# $app->autoload($directory, $extension = 'php') Autoloading


    // make any class/interface/namespace in any directory
    // loaded on demand
    // example:
    // make the '\vendor\' directory be autoloaded with it's 
    // files' extension 'php'
    $app->autoload('\vendor');
    // or with extension '.class.php'
    $app->autoload('\vendor', 'class.php');

# $app->go($to, $using = '302') Redirect helper


// this method helps you doing any type of redirection
// html,javascript (after some seconds) and php
// it also support Horus Router, if you want to
// do an internal redirection, just don't write
// url scheme (http,https, ... etc) ate the start
// but if you want to redirect to another site
// you must type the url schema .

# redirect to internal site url [/page/1] 
# using HTTP 302
$app->go('page/1');
# using HTTP 301
$app->go('page/1', 301);
# using HTML after 0 seconds
$app->go('page/1', 'html:0');
# using HTML after 5 seconds
$app->go('page/1', 'html:5');
# using Javascript after 3 seconds
$app->go('page/1', 'js:3');

# redirect to external url [http://google.com] 
# using HTTP 302
$app->go('http://google.com');
# using HTTP 301
$app->go('http://google.com', 301);
# using HTML after 0 seconds
$app->go('http://google.com', 'html:0');
# using HTML after 5 seconds
$app->go('http://google.com', 'html:5');
# using Javascript after 3 seconds
$app->go('http://google.com', 'js:3');

# $app->debug($state = true) show/hide errors


    // show errors
    $app->debug(true);

    // hide errors
    $app->debug(false);

# $app->statics returns object of "current memory usage, peak memory & exec-time"


Horus events "extend your application"

Horus Events helps you easily to extend your application, it works like actions/filters with no big code, just a two methods to listen / trigger them easily .

» Usage


    // load and start horus
    include 'Horus.php';
    $app = new Horus;

    // to add event:
    // $app->listen($tag, $callable, $priority);
    // $tag: is the category where $callable work under .
    // $callable: any valid php callback
    // $priority: the priority of the event, low number high priority .
    // examples:
    $app->listen('test.action.1', function(){
        echo "HI, i'm 1 ...";
    });
    $app->listen('test.action.1', function(){
        echo "HI, i'm 2 ...";
    });    
    $app->listen('test.action.1', function($arg){
        echo "HI, i'm 3 ...", " you have passed an argument it is: ", $arg;
    });

    // to run them just do this
    // and see the result
    // here we will pass an argument to the
    // event when triggering it, because
    // in the last event we want an argument
    // you can pass array of rags if there are
    // multiple args required
    $app->trigger('test.action.1', 'arg_1');

    // but how about filtering a varible,word, ... etc
    // let's register some filters
    // by default horus will store any returned
    // values from the last executed event
    // and store it in the last parameter
    // of the callback, so you can use it easily .
    // here we want at least one param,
    // one for the word that will be filtered
    $app->listen('test.filter.1', function($word){
        return "$word";
    });

    // here like the above one,
    // but we need the value that has been filtered
    // from last one
    $app->listen('test.filter.1', function($word, $last_filtered){
        // $word will be the new value
        // $last_filtered will be the last filtered value
        return "<h1>$last_filtered</h1>";
    });
    
    // let our word is:
    $myword = 'test';
    // now filter it (we will pass the $myword var)
    echo $app->trigger('test.filter.1', $myword);

    // what if the 'test.filter.1' has no event ?
    // yeah, this { $app->trigger('test.filter.1', $myword) }
    // will return null , so to set the default value for it
    // if null just pass a third param, with the value you need like this
    echo $app->trigger('test.filter.1', $myword, 'default value');

» Extend Horus

You can easily extend horus from its registered events,

    // * horus.output.before    --->    an action, before showing the output .
    // * horus.output.after     --->    an action, after showing the output .
    // * horus.output.filter    --->    is an output filter .

    // # Example:
    // minimize the output
    $app->listen('horus.output.filter', function($output, $filtered){
        $output = empty($filtered) ? $output : $filtered;
        return preg_replace('/\s+/', ' ', $output);
    });

    // to override 404 page
    $app->e404 = function(){
        echo '404 not found';
    };

Horus Container "Registry Pattern"

By default horus extending the container, so you can deal with horus as a container, the main Horus_Container implements the following interfaces: { ArrayAccess, Countable, IteratorAggregate, Serializable } .
it can be used as following :

    // load and start horus
    include 'Horus.php';
    $app = new Horus;

    // initialize it
    $obj = new Horus_Container

    // add a property
    $obj->myprop = 'value';

    // add method
    $obj->method = function(){
        echo 'test method';
    };
    $obj->method();

    // remove any thing
    unset($obj->myprop);

    // deal as array
    $obj['x'] = 'y';
    echo $obj->x;

    // export the registered data as array ?
    $data = $obj->export();
    
    // import data ?
    $obj->import(array(/* data here as k => v */));

    // you can also import on construction
    $obj = new Horus_container(array(/* ... */));

    // Note: Horus is already extending Horus_container
    // so you can deal with $app as $obj
    $app->xxx = 'yy'; // and so ...