A small, fast and minimal router for when you don’t need the overhead of a full-sized framework.

Getting Started with ToroPHP
Keine Kommentare

There’s no denying that there’s a plethora of PHP frameworks out there. From CakePHP to CodeIgniter to Symfony to Zend Framework; all these frameworks seem to promise the world because they’re loading with everything except the kitchen sink. So what happens if you just want something… light?

Enter ToroPHP.

ToroPHP is a lovely little PHP utility that I started contributing to last year. Toro doesn’t try to be anything that it isn’t; it’s merely a small routing application contained in a single file that’s positioned to be a bootstrap for you to quickly build and prototype RESTful PHP websites and applications. It doesn’t force you to load dependencies a particular way or require configuring (other than a basic Apache rewrite rule) or force a particular template engine onto you; it does one thing and one thing only and doesn’t impede or dictate, which is part of its charm.

As mentioned, ToroPHP is RESTful at heart so it makes it an ideal starting point for REST APIs or small web applications with clearly defined business objects.

While having this freedom can be liberating to experienced PHP developers, the same freedom can leave less experienced developers feeling isolated and lost. Hopefully this article mitigates that, as I’ll show you how to create a simple blog site with commenting functionality using ToroPHP as a base.

Understanding how ToroPHP works

Under the hood, ToroPHP uses handlers to act upon requests. A handler is a simple PHP class, and ToroPHP maps URLs to a handler. Unlike controllers, handlers instead have methods that correspond to a particular request method (GET, POST etc) and you’re free to implement as many or as few as you like.

Installing ToroPHP

The first step in using ToroPHP is actually obtaining it. Toro can be installed by simply downloading the file and including it in your PHP script. ToroPHP is also listed on Packagist, so it can also be easily installed using Composer. Simple add the following to the „require“ section of your composer.json file: „torophp/torophp“: „dev-master“ and then run php composer.phar install from the command line.

Apache re-writing

ToroPHP expects to handle all incoming requests. To do this, you need to create an Apache re-write rule to pass all incoming requests through index.php, where you’ll instantiate ToroPHP. A simple rule to ignore any other existing files and directories (such as images, style sheets and JavaScript files) looks as follows:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]

Creating routes

With ToroPHP installed, we can now begin using it. The first step is to define routes. This is basically a map of URLs ToroPHP should listen for, and the corresponding handlers it should pass a matching request to. The method called on the handler will be the same as the HTTP method used (more on this later). Create an index.php file and place this inside:

  'article/:alpha' => 'ArticleHandler'

If you’re astute, you’ll see we’re setting up routes for a basic blog site. We have a HomeHandler that will list articles on a GET request, and an ArticleHandler that will display an article requested by its slug.

Creating a database table

Before we can create handlers we need somewhere to store articles. We’ll store these in a database table (database name of your choice). Create a table called articles and add the following columns:

  • id (integer, unsigned, primary key, auto increment)
  • title (varchar, 60 characters)
  • slug (varchar, 60 characters, unique)
  • content (text)
  • date_created (timestamp, default: CURRENT_TIMESTAMP)

Feel free to insert a couple of test posts at this point too.

We now need to be able to connect to our database. Create a PHP script at includes/database.php and create a PDO object connecting to the database you created that’s held in a variable called $db. Include the file in index.php: include(‚includes/database.php‘);.

Creating our first handler

As aforementioned, handlers are simply PHP classes. They don’t extend any base classes; they just need to implement methods named after the HTTP request methods we want to respond to. So, we want a HomeHandler that responds to GET requests, and on doing so displays our blog’s articles. Create your first handler by creating a PHP script at handlers/HomeHandler.php and placing in it the following:

    $articles = $res->fetchAll(PDO::FETCH_OBJ);


Note: It’s considered bad practice to use global variables as above. You would normally use some form of dependency injection instead, but that’s outside the scope of this article. An alternative would be to create a class that implements the singleton pattern and yields a PDO instance, and call that inside handlers (i.e. $db = Database::getInstance()).

Moving on, add include(‚handlers/HomeHandler.php‘); to your index.php file. Our handler is querying the articles table for the last 10 articles, and displaying them in a template.

But what template?! We haven’t created it yet. Create a new file at templates/home.tpl.php as in the code above and place in it a simple loop:

    My Last Ten Blog Posts

With all being well, navigating to the root of your project’s folder in a browser should now yield a simple, unordered HTML list of titles of articles in your database, linking to the individual page for each.

Viewing a single article

We now need to create handler that responds to a user clicking the link of one of our articles. Article URLs will look something like http://example.com/article/some-article-slug. We need to create a new handler called ArticleHandler, and like our HomeHandler we’re going to implement one method that responds to GET requests. The only difference this time is, we’re passing a parameter that identifies the article we want to view.

    $stmt->bindParam(':slug', $slug, PDO::PARAM_STR);

    $article = $stmt->fetchObject();

    if (!$article) {


In this handler, we’re querying the database for a particular article. We use a prepared statement this time to avoid any SQL injection vulnerabilities. If no article matches the slug specified than we fire any registered hooks for handling 404 (not found) errors. If there’s a match, the handler proceeds to display the article in a template.

Like our homepage template, this can be quite simple. We only have one article to display, so there’s no need for a loop and our article.tpl.php template can be as simple as this:

    <?php echo htmlspecialchars($article->title); ?>

title); ?>

content))); ?>

Also, don’t forget to include our new ArticleHandler in index.php: include(‚handlers/ArticleHandler.php‘);.


In our ArticleHandler class you will have noticed a call to ToroHook::fire(‚404‘);. Currently, this does nothing. But built into ToroPHP is a hook system that allows us to execute code at certain points. There are five hooks currently available:

  • 404
  • before_request
  • after_request
  • before_handler
  • after_handler

Their names are pretty self-explanatory, with the latter four specifying when during execution they’re called. You define functions to be fired when a hook is called, so let’s create a function to be fired when we call the 404 hook.

Any 404 callbacks must be defined before our call to Toro::serve(). This means opening up index.php, and above our Toro::serve() place this:

Figure 6

ToroHook::add('404', function() {
    header('HTTP/1.0 404 Not Found');

This simply sends a 404 Not Found HTTP status code, and includes a template. Create the template templates/404.tpl.php and put a friendly error message saying the page could not be found. Execution is then stopped after this with the exit; call.

So let’s take stock of what we have so far:

  • ToroPHP routing requests
  • Home page handler that lists the last 10 articles in the database
  • An article handler that displays a specified article
  • A hook listening for 404 errors

What good is having a blog if no one can comment on it though?

So far you’ve seen handlers listening on standard GET requests. Well, it’s just as easy to act on other request methods such as POST. Let’s create a comments handler to demonstrate this.

Creating a comments handler

Before we create the handler we first need to create a route for it. In index.php simple add the following to the array passed to Toro::serve(): ‚/article/:alpha/comments => ‚CommentsHandler‘. This handler is going to have two methods:

  • get(), which will display comments for the specified article
  • post(), which handles a comment submission on a particular article

Create a database table called comments and add the following columns:

  • id (integer, unsigned, primary key)
  • article_id (integer, unsigned, foreign key for articles.id)
  • author_name (varchar, 60 characters)
  • author_email (varchar, 128 characters)
  • comment (text)
  • date_created (timestamp, default: CURRENT_TIMESTAMP)

We now create our comments handler. Following the pattern as before, we create a file at handlers/CommentsHandler.php and make sure we’re including it in our index.php file: include(‚handlers/CommentsHandler.php‘);. Now let’s create the actual handler code.

Displaying comments

The first step is to create a get() method that displays comments for a particular article. Let’s implement it:

    $stmt->bindParam(':slug', $article_slug, PDO::PARAM_STR);

    $article = $stmt->fetchObject();

    if (!$article) {

    // get comments for specified article
    $sql = "SELECT * FROM comments WHERE article_id = :article_id ORDER BY date_created ASC";

    $stmt = $db->prepare($sql);
    $stmt->bindParam(':article_id', $article->id, PDO::PARAM_INT);

    $comments = $stmt->fetchAll(PDO::FETCH_OBJ);



Our get() method first looks up the ID of article specified by the slug in the URL (like http:://example.com/article/some-article-slug/comments) and then uses it in both a second query to find comments for that particular article, and then also used in our template:

    Comments for <?php echo htmlspecialchars($article->title); ?>

Comments for title); ?>

  1. author_name); ?> said:

    comment); ?>

Add comment

In our comments template, we’re also including a HTML form that captures a commenter’s name, email address and what they want to say should they wish to comment. If you’ve ever visited a blog then this type of comment form won’t look too unfamiliar. This form when submitted will trigger our handler’s post() method. And with that said…

Handling comment submissions

Since the HTML form submits to itself and has a method of post, this will create a POST request to the same URL, in which case we need to add a post() method to our CommentsHandler.

public function post()
  global $db;

  // perform validation here!
  // re-assign sanitised data, i.e. $article_id = $_POST['article_id'];

  $sql = "INSERT INTO comments (article_id, author_name, author_email, comments)
          VALUES (:article_id, :author_name, :author_email, :comments)";

  $stmt = $db->prepare($sql);
  $stmt->bindParam(':article_id', $]article_id, PDO::PARAM_INT);
  $stmt->bindParam(':author_name', $author_name, PDO::PARAM_STR);
  $stmt->bindParam(':author_email', $author_email, PDO::PARAM_STR);
  $stmt->bindParam(':comments', $comments, PDO::PARAM_STR);

  // here you could set a success message in the session and then redirect to the comments page

All being well, this should create a record in our comments table in our database with the submitted data.

Do bear in mind that there are two parts of the post() method that have been left to fill out. The first is the most important: validation. It’s important to validate the data. Some checks you’ll need to perform at the very minimum are:

  • The name is valid (maybe greater than a couple of characters to allow for names like “Bo”, „Li“ etc)
  • The email address is valid (PHP has a built-in filter_var() method and FILTER_VALIDATE_EMAIL constant that’s good for this)
  • The comment is valid, whether that’s greater than a certain number of characters or contains a minimum number of words; that’s for you to decide
  • The article ID is valid (and has not been tampered with on the client side)

You could also add further validation, such as capturing the visitor’s IP address upon submission, or implementing a third-party spam checking service like Akismet. A good PHP project for integrating Akismet in your own PHP applications can be found at https://github.com/achingbrain/php5-akismet.

As well as validation you need to decide what will happen after the comment has been submitted. If there are errors you could redirect back to the get() view with an error message detailing what data was missing/invalid. Similarly on success you could redirect to the get() view with a success message instead.


So far we’ve gone over the following:

  • Defined some routes
  • Created some basic handlers that respond to different HTTP methods
  • Displayed results from a database in templates
  • Looked into hooks in ToroPHP

Hopefully this will get you well on your way with ToroPHP and creating your own websites and web apps with it. It’s great for prototyping websites and web apps, and due to its RESTful nature also makes a great starting point for REST APIs.

For more examples, you can check out the official GitHub repository. I’ve also written a separate blog post on integrating a template engine like the popular Smarty.

Now, go forth and start building the Internets! Toro!

Martin is a young but experienced developer based in Newcastle upon Tyne, UK specialising in both front-end development and server-side development with the LAMP stack. He’s equally adept with HTML and CSS as he is with PHP and related technologies. After plying his trade in agencies for four years Martin went freelance in 2011 and hasn’t looked back since, working for clients such as ScS and Ubisoft Reflections. In his spare time, he contributes to open source software, maintaining his own projects as well as contributing to others. You can follow Martin on Twitter.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Benachrichtige mich bei
Inline Feedbacks
View all comments
- Gib Deinen Standort ein -
- or -