Create a Poll with PHPixie

When choosing a PHP framework you need to make sure that it emphasizes features which are the most important to you. If you are looking for something fast, simple, and easy to learn than PHPixie may be a perfect choice.

To illustrate the basics of developing with PHPixie we will create a small polling application. By the end of this tutorial you will have a working poll application and an understanding of how easy it is to code projects using PHPixie.

Before going on, make sure you download PHPixie from the project’s website and extract the downloaded archive into the root web directory of your web server. Visit http://localhost/ (or other address as appropriate) in your browser and you should see the ‘Have fun coding!’ welcome message.

Creating the Database

First we’ll need to create two tables in the database: polls will store the topic we would like to get opinions for, and options will contain possible answers for each poll and the amount of votes each received.

Open up your MySQL client and run these queries:

CREATE TABLE polls (
    id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
    topic VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE options (
    id INTEGER NOT NULL AUTO_INCREMENT,
    name VARCHAR(255) DEFAULT NULL,
    poll_id INTEGER UNSIGNED NOT NULL,
    votes INTEGER NOT NULL DEFAULT 0,
    PRIMARY KEY (id),
    FOREIGN KEY (poll_id) REFERENCES polls(id)
);

To configure the database connection, we edit the database.php file inside the application/config directory:

<?php
return array(
    'default' => array(
        'user' => 'dbuser',
        'password' => 'dbpassword',
        'driver' => 'pdo',

        // 'Connection' is required if we use the PDO driver
        'connection' => 'mysql:host=localhost;dbname=polls',

        // 'db' and 'host' are required if we use Mysql driver
        'db' => 'polls',
        'host' => 'localhost'
    )
);

PHPixie can be configured to use multiple database connections, but since we are working with just the single database we specify it as default. That’s all the configuration required, so we can move to writing code now.

Models

A common way to describe the Model-View-Controller architecture is as follows:

  • Models represent data objects we work with (polls and options in our case)
  • Controllers handle user requests and are the heart of our application
  • Views are templates that controllers use to display the response

Typically, models handle operations like retrieving data from the database and saving it. PHPixie takes care of these operations using an ORM layer so we don’t need to write any database queries at all. We just need to define our models like this:

<?php
class Poll_Model extends ORM {
    // Each poll can have many options
    public $has_many = array('options');

    // This way we can define some additional
    // dynamic properties for a model.
    // Later on we will be able to access it via $poll->total_votes
    public function get($property) {
        if ($property == 'total_votes') {
            $total = 0;
            foreach ($this->options->find_all() as $option) {
                $total += $option->votes;
            }
            return $total;
        }
    }
}

The above code should be placed in application/classes/model/poll.php.

Relationships may be defined using 3 arrays: belongs_to, has_one, and has_many. The first (belongs_to) defines a one-to-one relationship when a table references another table using a foreign key, in our case options belongs to polls using the poll_id field. The foreign key is automatically guessed using model names by PHPixie. On the other side of the belongs to relationship falls has_many, in our case each poll has many options. has_one is a special case of has_many, limiting the amount of referenced objects to one. To define a relationship, we just need to add the name of the model we want to the appropriate array. A more thorough relationship configuration is possible, with the ability to specify which field to use for the relationship, set a different name for it, or to create a many-to-many relationship via a linking table, but in our case using this simple approach is enough.

The find_all() method looks up records in the database. When we reference the options property, ORM is silently building a database query to select all of the options for the current poll. Calling find_all() subsequently executes that query and returns these options also as ORM models.

Overriding the get() method allows us to add properties to our model dynamically. If a property we are trying to access is not found in the model class, ORM will call the class’ get() method passing the name of the property as an argument. Any result that get() will return will be considered the value for that property and stored inside the model, so that when we access the property once more we will get the cached value. As internal model operations usually invoke some kind of database action, it is preferable to have data cached, so that’s why we’re overriding get().

Note that while get() is often used to cache data from find_all(), it is not meant only for that. For example, we could use get() to define a property that we would access multiple times but should only be retrieved once.

This internal cache persists only for the current object. Of course if you don’t want this behavior at all it is always possible to add your own total_votes() method to the model that will recalculate the votes each time it is called.

The code for the options model is placed in application/classes/model/option.php:

<?php
class Option_Model extends ORM {
    public $belongs_to = array('poll');

    public function get($property) {
        if ($property == 'percent') {
            if ($this->poll->total_votes == 0) {
                return 0;
            }
            return floor($this->votes / $this->poll->total_votes * 100);
        }
    }
}

This model follows similar logic as the previous one. We define a belongs_to relationship with polls and a percent property, the only difference being that for one-to-one relationships it is not necessary to call the find_all() method, so we can access the poll that this option belongs to just as if it was a property.

PHPixie relies heavily on naming conventions to save us time when creating models, although it doesn’t force us to name everything according to these conventions (it’s easy to alter this behavior). Here we’re following the convention of using the plural form when naming tables in the database and singular form when naming models.

Basic Controller

The URL for a specific action by default is /// e.g. http://localhost/polls/add. It is also possible to pass an extra ID parameter after the action which we will use to access individual polls.

Our controller will have to know how to display three types of pages:

  • Main page with all the polls listed
  • A single poll page where we can see current results and cast a vote
  • A form to add a new poll

Each page must have an action defined for it inside the controller and we will need a HTML template for every page too. So, a simple Controller that just displays the templates would look like this:

<?php
class Polls_Controller extends Controller
{
    public function action_index() {
        // This is how we load up a template
        $view = View::get('index');
        $this->response->body = $view->render();
    }

    public function action_poll() {
        $view = View::get('poll');
        $this->response->body = $view->render();
    }

    public function action_add() {
        $view = View::get('add');
        $this->response->body = $view->render();
    }
}

A big issue with this setup is that there would be a lot of code repeated inside the templates because every page needs a header and footer. The solution is to create one general template and then include subtemplates inside it. We can do this using the before() and after() methods defined inside the controller and they will execute respectively before and after an action is called. Now our controller will look like this (save the following as application/classes/controller/polls.php):

<?php
class Polls_Controller extends Controller
{
    protected $view;

    public function before() {
        // We load up the main view and
        $this->view = View::get('main');

        // Now we find a full path to a view that has
        // the same names as the action to be excuted
        $template = Misc::find_file('views', $this->request->param('action'));

        // We pass the view we located to our main template.
        // All properties assigned to a view will be available
        // as variables inside the template
        $this->view->template = $template;
    }

    public function after() {
        // After an action completes we render the view
        $this->response->body = $this->view->render();
   }

   public function action_index(){
        $view = View::get('index');
        $this->response->body = $view->render();
    }

    public function action_poll() {
        $view = View::get('poll');
        $this->response->body = $view->render();
    }

    public function action_add() {
        $view = View::get('add');
        $this->response->body = $view->render();
    }
}

We can pass any variables we like to the view just by assigning them as properties. Now before we add any more code to the controller, let’s take a look at the views themselves.

HTML Layouts Using Views

Views are basically HTML files with inserted PHP code to display the values assigned to place-holder variables from inside the controller. Our main view is quite small as all it has to do is present some common HTML and include a correct subtemplate. The following is application/views/main.php:

<!DOCTYPE html>
<html>
 <head>
  <title>PHPixie polls</title>
  <link href="https://netdna.bootstrapcdn.com/twitter-bootstrap/2.2.2/css/bootstrap-combined.min.css" rel="stylesheet">
  <link href="/style.css" rel="stylesheet">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
 </head>
 <body>
  <div class="container">
   <div class="span4"></div>
   <div class="span4">
    <h2>PHPixie Polls</h2>
    <!-- Here is where we include a subtemplate -->
<?php include($template);?>
   </div>
   <div class="span4"></div>
  </div>
 </body>
</html>

We’ll use Bootstrap as a base for our page styles so we don’t have to concentrate too much on the design. The only modifications we need to make to the CSS are in the web/style.css file.

.nav .muted {
    float: right;
}

td form {
    margin-bottom: 0px;
}

.filled {
    background: #08C;
    height: 20px;
}

.bar {
    width: 100px;
}

Now we can create the individual pages.

Creating Individual Pages

The polls page should just present a list of available polls. It only requires a slight modification to controller/polls.php.

<?php
//...
public function action_index() {
    // We pass all stored polls to the view
    $this->view->polls = ORM::factory('poll')->find_all();
}

The view will for the page will be a simple list with a link to the poll creation page. The following is code for views/index.php:

<ul class="nav nav-tabs nav-stacked">
<?php
foreach($polls as $poll) {
?>
 <li>
  <!-- This is how a link to a single poll will look like -->
  <a href="/polls/poll/<?=$poll->id;?>"><?=$poll->topic;?>
   <div class="muted"><?=$poll->total_votes; ?> Votes</div>
  </a>
 </li>
<?php
}
?>
</ul>
<a class="btn btn-block" href="/polls/add"><i class="icon-plus"></i> Add a Poll</a>

If we were to add some data to the database at this point we would see the following when accessing http://localhost/polls/index (index can be omitted from the URL as it is the default action):

phpixie-01

Creating polls requires a bit more work. We have to check if the form was submitted, if so we save a poll and redirect the user back to the listing, otherwise we display the form. Add the following update to controller/polls.php:

<?php
//...
public function action_add() {
    // We only need to perform this if the form was submitted
    if ($this->request->method == 'POST') {

        // Creating a new poll
        $poll = ORM::factory('poll');

        // This is how you access POST form data
        $poll->topic = $this->request->post('topic');

        // Save the poll to the database
        $poll->save();

        // Similarly we create and save options specified for this poll
        foreach($this->request->post('options') as $name) {
            if (empty($name)) {
                continue;
            }
            $option = ORM::factory('option');
            $option->name = $name;
            $option->save();

            // We add each option to the 'options' relation we defined for the poll
            $poll->add('options', $option);
        }

        // This will prevent after() from executing
        // we need to do this because there is no point of returning any data
        // if we redirect the user
        $this->execute=false;
        $this->response->redirect('/polls/');
        return;
    }

    // If the form was not submitted the after() method will
    // take care of showing the creation form
}

There is nothing special about the template for poll creation. Use the following to create views/add.php:

<script>
$(function() {
    $('#addOption').click(function(evt) {
        evt.preventDefault();

        var newOption = $('.option:eq(0)').clone();
        newOption.find('input').val('').attr('placeholder', 'Option #'+($('.option').length + 1));
        $('#options').append(newOption);
    })
})
</script>
<form method="POST">
 <fieldset>
  <legend>Add a new poll</legend>
  <label>Topic</label>
  <input type="text" placeholder="Type your topic here..." name="topic">
  <label>Options</label>
  <div id="options">
   <div class="option">
    <input type="text" name="options[]" placeholder="Option #1">
   </div>
   <div class="option">
    <input type="text" name="options[]" placeholder="Option #2">
   </div>
   <div class="option">
    <input type="text" name="options[]" placeholder="Option #3">
   </div>
  </div>
  <button class="btn" id="addOption"><i class="icon-plus"></i> Add option</button>
  <button class="btn btn-primary"><i class="icon-ok icon-white"></i> Save poll</button>
 </fieldset>
</form>
<a class="btn btn-link" href="/polls">&lt; Back to polls</a>

Note that we specified the name for the option text input like options[], this way it will be passed as array of options to PHP. We also added a convenient Add Option button to add additional rows. The result should look as follows:

phpixie-02

I hope at this point you can already predict some of the code you will see here. This controller has to fetch the poll specified by the ID in the URL, and it should also handle users voting for an option. Again, make the following updates to controller/polls.php:

<?php
//...
public function action_poll() {

    // Handle voting
    if ($this->request->method == 'POST') {

        // If an option is was supplied via POST we increment
        // its votes field by 1
        $option_id = $this->request->post('option');
        $option = ORM::factory('option')->where('id', $option_id)->find();
        $option->votes += 1;
        $option->save();

        // Now we redirect the user back to current polls' page
        // This is done so that refreshing a browser window will not
        // produce multiple votes
        $this->response->redirect('/polls/poll/' . $option->poll->id);
        $this->execute = false;
        return;
    }

    // You can get the url id parameter using param()
    $id = $this->request->param('id');
    $this->view->poll = ORM::factory('poll')->where('id', $id)->find();
}

The find() method acts just like find_all() but it returns only the first result. Inside the view we want to draw bars that would depict the relative amount of votes, and this is why we added that percent property to the options model earlier. The following code is for views/poll.php:

<h3><?=$poll->topic;?></h3>
<table class="table">
<?php
foreach ($poll->options->find_all() as $option) {
?>
 <tr>
  <td><?=$option->name;?></td>
  <td><?=$option->votes;?></td>
  <td class="bar">
   <div class="filled" style="width:<?=$option->percent;?>%;"></div>
  </td>
  <td>
   <form method="POST">
    <input type="hidden" name="option" value="<?=$option->id;?>">
    <button class="btn btn-mini">Vote</button>
   </form>
  </td>
 </tr>
<?php
}
?>
</table>
<a class="btn btn-link" href="/polls">&lt; Back to polls</a>

And this is how it will look like:

phpixie-03

Almost Done

Before we declare our application complete we still need to make sure it is available by visiting http://localhost/. To do this, we have to change the default controller in the application/config/core.php configuration file from ‘home’ to ‘polls’ like this:

<?php
return array(
    'routes' => array(
        array('default', '(/<controller>(/<action>(/<id>)))', array(
            'controller' => 'polls',
            'action' => 'index'
            )
        )
    ),
    'modules' => array('database', 'orm')
);

Now we have a fully functional poll application as well as a grasp on how PHPixie handles things and how easy it is to develop with it. If you’d like to download the code for this article to explore, you can find it on GitHub.

There is still a lot to learn though. Things like custom routes and complex ORM relationship can help you tailor your project even more. Learn more about them by visiting the PHPixie website.

Image via Fotolia

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://twitter.com Amy Stephen

    Hey Roman – Thanks so much for sharing your work. Am following you on github now – I think you have a great future.

    What I like about your framework is that it gives people a clear look at the phases a webapp goes thru — in a very simple, elegant, easy to understand way. I appreciate that.

    I have a few concerns. Most importantly, I’d be careful with examples that don’t demonstrate good security practices. Chris Shiflett says the top 2 security practices are “Filter input. Escape output.” Might be good to include those in published examples and also within the framework. http://shiflett.org/blog/2005/feb/my-top-two-php-security-practices

    Also curious about the use of statics with some of your application-wide classe. It doesn’t seem necessary and such an approach can create problems that aren’t always discovered right away. Might be worth revisiting some of that.

    Also, mamespaces are important – especially with PSR-0 https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md and also unit testing.

    Thanks for sharing! Looking forward to watching your progress!

    • http://phpixie.com PHPixie

      Thanks =)
      Yes, I should’ve mentioned strip_tags() for input filtering, which would be enough for the purposes of this example.
      The static methods mostly act as factory methods for instances, with the exception of some rare cases (like searching files across the CFS), it is not that difficult to mock them for unit testing, actually I’m writing tests for them right now.
      As for PSR-0, as you might’ve noticed PHPixie takes a somewhat reversed approach to naming classes, e.g. Home_Controller instead of Controller_Home for /controller/home.php. The name of the file (home) usually carries the most information about the class, so it’s a bit easier to read the code this way, especially for long class names. This doesn’t cause any problems with vendor libraries though, as Composer is supported.

  • Johny

    Sorry to say it straight. When I saw a new Framework that using Class::method() style, I feel like why you bother create a framework if you don’t know what’s wrong with ‘static’.

    • http://phpixie.com PHPixie

      It’s totally possible to create Views and ORM models using ‘new’, statics are mostly factories and shortcuts and are totally avoidable in most cases =)

  • Gyr

    I liked your explanation of model-view-controller. It is the first time I have been able to understand these concepts. So thank you for that!

  • Brett

    Hi Roman – thanks for your in-depth article on PHPixie, it was well written and the examples are easy to follow.

    I regularly use a number of different PHP frameworks depending on the website and what it is required to do.

    Had I read this article 5 years ago, I would have definitely investigated PHPixie framework further. The fact is, there are now so many well established PHP frameworks already with huge communities and well thought through and tested codebases that it’s difficult to get excited about yet another PHP framework.

    Where does PHPixie fit amongst the existing PHP frameworks?

    Below are a list of PHPixie’s main points from the website. I’ve added how I think each point compares with other PHP frameworks:
    1) very light-weight — is PHPixie more light-weight than Silex or Yolo? Probably not.
    2) well documented — is PHPixie better documented than any other major PHP framework? Probably not.
    3) automagic (naming conventions) — is PHPixie using PSR-0/1/2/3 and PHP 5.3 namespaces? No.
    4) super fast (performance) — is PHPixie faster than Silex? Probably not.
    5) rapid development — using PHPixie, can a developer build a CRUD faster than in Symfony2 (using a Doctrine entity) or quickly internationalize dates for a visitor like with Zend? No.

    Before I get flamed for picking Silex above instead of your preferred PHP framework, I mentioned Silex because in my opinion, when you need a light-weight framework, it is sufficiently light-weight and performant. Also with Silex and any other PHP framework for that matter, you can easy cherry-pick components from Zend Framework!

    There could very-well be a more light-weight and performant PHP framework available – and that’s my point – there’s probably already a major framework offering feature X, which is well documented, uses PSR-X, PHP 5.3 namespaces, 90%+ code coverage etc etc and has been thoroughly tested on high-traffic websites.

    Performance is always going to be a concern but it shouldn’t be a developers one-and-only concern when picking a framework. For example, Zend Framework and Symfony have come a long way on the performance front, particularly in version 2 of both. Why do developers use these frameworks? Each framework comes with a suite of amazing ready-to-use structure/components which have been thoroughly tested in the wild saving developers hundreds of hours of development time.

    Why re-invent the wheel?

    • http://phpixie.com Roman

      Thanks for such an extended comment, I’ll try to answer everything in an olderly fashion =)
      Well there are a few reasons to reinvent wheels actually:
      1) Silex is a microframework, it doesn’t provide you with a database access layer, ORM nor a templating system. PHPixie has those things, and some more available as modules. Basically you can develop a website using PHPixie alone, and you would have to use additional libraries to do so in Silex.
      2)Actually it is documented quite well, the reason being there is not that much code in it, and there are tutorials written for practically everything she can do on the website. It’s harder to document a larger framework like Symfony because of the amount of things it has.
      3)You misunderstood automagic a bit, it meant that a lot of things don’t require configuration, like setting up ORM etc. Supporting PSR-1/2/3 wouldn’t have anything to do with magic as those concern more code formatting and some interfaces. PSR-0 classes can be loaded using composer support.
      4) PHPixie parses paths on par with silex actually. The speed overall comes from delivering a full stack (datatabase, orm, views etc) with limited features, which cover 90% of what the developer uses. ORM in PHPixie works much faster than Doctrine does, because Doctrine was actually designed to handle a lot of things in a lot of ways.
      5)Actually doing CRUD is quite easy and straightforward, it won’t generate a page for you like Yii does, but you can get CRUD functionality just by extending ORM which is pretty fast. Internationalizing dates is a library feature and has little to do with a framework actually, you could always use an external class for that.
      Basically PHPixie holds the middle ground between large frameworks like Zend and small ones like Silex.
      Consider you want to create a meme gallery website or a “famous sayings” website, do you really need half of the features that Zend has? Would you still want to have database access and MVC architecture out of the box? Would you probably be ultimately host it on a shared hosting that may not be very powerful? If so, PHPixie would be the best choice for you =)

  • joe

    Very nice!
    However it could be that I am missing something, but I get the following error
    call_user_func() expects parameter 1 to be a valid callback, non-static method Debug::render_error() should not be called statically in /Applications/MAMP/htdocs/public/system/classes/debug.php on line 68ob_end_clean(): failed to delete buffer. No buffer to delete in /Applications/MAMP/htdocs/public/system/classes/debug.php on line 27Cannot modify header information – headers already sent by (output started at /Applications/MAMP/htdocs/public/system/classes/debug.php:114) in /Applications/MAMP/htdocs/public/system/classes/debug.php on line 38Cannot modify header information – headers already sent by (output started at /Applications/MAMP/htdocs/public/system/classes/debug.php:114) in /Applications/MAMP/htdocs/public/system/classes/debug.php on line 39
    ……
    strip_tags() expects parameter 1 to be string, array given