PHP “No Framework” Example Application on Rails

Rasmus Lerdorf has invented The no-framework PHP MVC framework. He argues that frameworks are bad:

“a big complex MVC framework with plenty of layers that abstracts away your database, your HTML, your Javascript and in the end your application itself.”

I’m a big fan of frameworks, especially good ones like Ruby on Rails, and I wanted to prove that Rasmus Lerdorf is wrong. Please don’t believe me too fast, since I’m obviously heavily biased.

The Application

Screenshot of the application on Rails

The example application is a product manager. You have a list of products, click on one of them and edit that one. Every product has a name, description, category and price. You can create new products too. Boring boring.

The interesting thing is the Ajax. The application uses Ajax to validate your input. If you don’t enter correct data in the fields, these fields will be highlighted.

The Conversion

The conversion was pretty easy. I hope I have achieved a Railsish style. To do this, I didn't translate his code, but I looked at the application, and coded the same thing in Rails. The risk of doing it in a PHP-style in Rails is too big. PHP-style in Rails will probably not work. The problem is now that there is a chance that my application is wrong or incomplete. Please tell me if it is. I’ll describe the process in the next paragraphs, roughly in the order I converted everything.

The Model

The model handles the data in the database. Rasmus’ application had 2 files for the model: db.inc and items.inc. The former handles the database connection, and the latter creates functions which handle the products. I don’t have to establish a connection manually, because it is built into Rails.

I first started RadRails, a Rails IDE. Then I created a new project, called beat-php ;-). After that, I set up a mysql database called beat-php_development. Rails can figure out the database automatically if you use this naming. The last step is the creation of the table. I created a table called products:

id - integer
name - varchar
description - varchar
category - varchar
price - varchar

Yea, that’s right, the price is a varchar too. I believe the original application used this too, because you can enter letters in the price field, and they are saved correctly.

PHP needs to create a database connection first (plus some other things). We don’t need a Rails equivalent, because it is done automatically for us. Rails does offcourse need to know which database we’re using (and password, etc.), but the defaults are OK for a development machine.

Database setup:

Imagine how much time we have saved already, and imagine that nearly every application needs a database. Rails helps us a lot by using sensible defaults for a lot of things. If you stick with these defaults, Rails will save you even more time.

The next thing is the model for the products, or items table. Rails gives us a code generator for this. The code generator just creates a file with a empty class in it. Because I’m using RadRails, I can right-click in the file view, and then new > model > Product. This creates this file:

class Product < ActiveRecord::Base
end

By declaring this, we can do things like:

Code                    Executed SQL 

Product.find_all     => SELECT * FROM products
Product.find(1)      => SELECT * FROM products WHERE id=1

It gives us an interface to the products table. The functions have already been declared in the ActiveRecord::Base class, so we don’t have to declare these ourselves. They become part of the Product class by inheritance (< in Ruby is the extends keyword in PHP). Rails maps a class named Product to a database table called products. (Rails knows the plural of product, and uses that name).

The model is part of the MVC approach: we write an interface to our data for our application. One important thing to remember is that the model should be usable no matter which user interface it is using. So we should be able to turn our application in a desktop version, without making any changes to the model.

Product model:

The Controller

The controller handles the requests of the user. Every request goes through a controller first. The controller uses the model and view to create a response. In this application, one of the tasks of the controller is to load a list of products, using the model. The controller gives this list to the view, which turns it into HTML.

  1. User visits a page
  2. Controller uses the model to get data
  3. Controller instructs the view to create a HTML page with this data

I used another Rails generator to create a controller:

class ProductsController < ApplicationController
end

This is a controller. It’s name is ProductsController. Rails uses the name of the controller to create pretty urls. Let’s fill it in with some code:

class ProductsController < ApplicationController
  def index
    @products = Product.find_all.each{|p| p.price = '$' + p.price.to_f.to_s}
    @product = Product.new
  end
  
  def update
    @product = @params[:id] ? Product.find(@params[:id]) : Product.new
    render :update do |page|
      if @product.update_attributes(params[:product])
        page.send :record, 'document.location = "'+url_for(:controller => 'products', :modified => true)+'"'
      else
        @product.errors.each do |attr, msg|
          page.visual_effect :highlight, "product_#{attr}", :startcolor => '"#ff0000"'
        end
      end
    end
  end
  
  def form
    @product = Product.find(@params[:id])
    render :partial => 'form'
  end
end

I have defined 3 methods. Methods in a controller are called actions. We can visit these actions with a webbrowser:

http://ourapplication.com/products/update executes the update action. http://ourapplication.com/products/index is for the index action, but because the name of the action is index, we could have visited http://ourapplication.com/products/ too. It works like an index.html on most servers.

The update and form actions will not be visited by users. They are meant to be called with Javascript using Ajax. Javascript code calls these actions, and uses the data they return. The update action returns Javascript code to be executed, the form action returns the filled-in form if the user clicks on one of the products in the list, and the index action outputs a HTML page (the main page).

If you want to understand the code, you’ll have to learn how to use Ruby and Rails.

Rasmus Lerdorf handles the validation in the controller. This is wrong, because data validation should happen in the data model. It is partially right too. Sometimes it is better to invent your own rules instead of listening to others. Anyway, we validate data in the model, because that is the easy way in Rails:

class Product < ActiveRecord::Base
  validates_presence_of :name, :description, :category, :price
end

This is how it works:

a_product = Product.new(:name => 'Apples', :category => 'food', :price => 2.65)

if(a_product.save())
  print 'Saved the product'
else
  print 'The product could not be saved'
end

This will print ‘The product could not be saved’, because a_product does not have a description. Thus, save() returned false. (The description was not present)

The PHP application used a lot of Javascript. So:

The View

The view is rather similar.

Rasus Lerdorf chose to use two view files. One for the main layout, a header and a footer, and another for the table and the form.

I found it easier to create one main view, which includes the product list, and another file for the form.

This is the main view:

<html>
  <head>
    <title>Products - Powered by Rails</title>
    <%= javascript_include_tag :defaults%>
    <%= stylesheet_link_tag 'style' %>
  </head>
  <body>
    <div id="wrap">
      <h1>The no-framework PHP MVC framework's example application ported to Rails</h1>
      <div id="form"><%= render :partial => 'form' %></div>
      <table id="products" cellspacing="0">
        <% for product in @products %>
        <tr class="<%= cycle('even', 'odd') %>" id="product_<%= product.id %>" 
            onclick="<%= remote_function :update => 'form', :url => {:id => product, :action => 'form'} %>">
          <% for column in [:name, :category, :price] %>
          <td class="<%= column %>"><%= product.send(column) %></td>
          <% end %>
        </tr>
        <% end %>
      </table>
      <% if @params[:modified] %>
        <div id="modified">modified</div>
        <%= javascript_tag(visual_effect :fade, 'modified', :duration => 2) %>
      <% end %>
    </div>
  </body>
</html>

I can’t remember how to link a stylesheet to a HTML document. <link rel="??" type="???" src|href="url">. In Rails, I do no longer have to. There is a built in function: stylesheet_link_tag('style') gives me the correct link tag for stylesheets/style.css.

To create odd-even colored rows, PHP needs to use a ugly counter variable. I used the built-in Rails function cycle(). cycle('even', 'odd') returns ‘even’ on the first call, then ‘odd’, ‘even’, ‘odd’, etc.

The form view:

<% form_remote_for :product, @product, :url => {:action => 'update'} do |p| %>
  <%= @product.new_record? ? '' : hidden_field_tag(:id, @product.id) %>

  <label for="product_name">name:</label><%= p.text_field :name %>

  <label for="product_description">description:</label><%= p.text_area :description %>

  <label for="product_category">category:</label>

  <%= p.select(:category, ['apparel', 'computers', 'food', 'miscellaneous', 'weird stuff'],
       :include_blank => true) %>

  <label for="product_price">price:</label><%= p.text_field :price %>
  <%= submit_tag 'Save' %>
<% end %>

form_remote_for is a function which builds Ajax forms. Instead of coding these by hand, it is as easy as text_field :name. This creates a text field which maps to the name column in the database. No <select ...><option ...>... by hand. Just select :column, options.

View:

Conclusion

I found Rasmus Lerdorf’s ‘framework’ good. He separated the M, V and C (except for the validation). It is probably better than most other frameworks. The only thing it needs is helper code to make tasks like data validation and form generation easier. If you put the models, views and controllers in separate directories it will be good for bigger projects too. You’ll still need to add many include statements manually. This adds up for big projects, so an "automatic includer" would be helpful too.

A good framework can really help. I’ve been a PHP programmer myself, and the creator of Rails has been a PHP programmer too. He tried to create his framework in PHP, but the language was too limited for his needs. He needed read/write introspection, and PHP has nearly no write introspection. Write introspection does things like adding methods to classes at runtime. If PHP has these things, they are certainly not mainstream. I think it is impossible copy Rails in PHP, but as frameworks influenced by Rails like Cake, and many other frameworks have shown, you will still benefit from a good framework.

The thing is, why not try the real thing? Give Rails a try, and experience the pleasure of a very productive environment. Do not fear Ruby, it’s a nice little language. Ruby is responsible for a large part of the productivity boost in Rails. And I think all Ruby/Rails programmers will benefit from a larger community.

The numbers:

For every line I wrote, Rasmus Lerdorf wrote more than 6 lines.

This is not like the 20x productivity boost versus Java, but not bad.

Whoops, I have forgotten the CSS file. Here it is.

If you’re interested in Rails, the Rails website is for you. There is information about both Ruby and Rails on the wiki.

If you have any questions, email me (also for spelling mistakes, english is not my native language)