Level: Intermediate Duane O'Brien (d@duaneobrien.com), PHP developer, Freelance
04 Dec 2007 A common criticism of early versions on PHP was that they did not support
Model-View-Controller (MVC)-style architectures. Today, developers can chose from many
PHP frameworks. This "PHP
frameworks" series takes a look at three widely used PHP frameworks — Zend,
symfony, and CakePHP — examining their similarities and differences while
building and extending a sample application in each of the three frameworks. Part 1
lays out the scope for the series and gets the prerequisites out of the way. In Part
2, you build the sample application in each of the three frameworks. Here in Part 3,
you will extend the application and look at exceptions to the
rule.
About this series
This series is designed for PHP developers who want to start using a framework, but
have not examined the available frameworks in detail. This series examines why the
three frameworks under examination were chosen, how to install each,
and you'll get a good handle on the test application that you're going to extend in
each framework. It might sound like a lot, but don't worry — we break it down
into manageable chunks.
Part 1 of this series lays out the scope for the series, introduces the frameworks
being examined, covers their installation, and scopes out the first test application you will build. (Phew!)
Part 2 walks you through building the sample application in each of the three
frameworks, highlighting their similarities and differences.
Part 3 starts with extending the test application, then deals with exceptions to the
rule. All frameworks work well when doing the tasks for which they were designed.
Needing to do something the framework wasn't built to do happens on every project. This
article looks at such instances.
Part 4 focuses primarily on Ajax support. The use of Ajax, using native code and
third-party libraries, is examined — specifically, how each framework
behaves and accepts specific popular libraries.
Part 5 deals with working outside the frameworks. A single task is identified (nightly
update script), and the process for accomplishing this task is examined in each
framework.
About this article
This article walks through extending the sample application, Blahg, in each of the
three frameworks (Zend, symfony, and CakePHP). Then we look at dealing with exceptions
to the rule. Most frameworks perform well when the application is designed for the
framework. However, what happens when an application just won't conform?
You should have already gone through and understood Part 1 and Part 2 of
this series.
Extending Blahg
At the end of Part 2, it
was suggested that you might try extending Blahg to add a comments controller, to
allow readers to respond to a particular post with their own insightful, creative, and
meaningful input. It was also suggested that you didn't need to break your back
integrating the comments and posts controller, since you'd get some code in this
article to outline how that works. Of course, it works differently, depending on the
framework you're using at any given time. But regardless of the framework, we need a comments table.
Creating the comments table
For the Zend Framework and symfony, use the following SQL to create a comments table.
Listing 1. Creating a comments table in Zend Framework and symfony
CREATE TABLE 'comments' (
'id' INT( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
'post_id' INT( 10 ) NOT NULL ,
'text' TEXT NOT NULL ,
'created' TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE = MYISAM ;
|
For CakePHP, use the following SQL.
Listing 2. Creating a comments table in CakePHP
CREATE TABLE 'comments' (
'id' INT( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
'post_id' INT( 10 ) NOT NULL ,
'text' TEXT NOT NULL ,
'created' DATETIME DEFAULT NULL
) ENGINE = MYISAM ;
|
With comments tables in place, we can get down to business by adding the comments functionality.
Adding comments with the Zend Framework
Adding comments with the Zend Framework looks similar to what we did when we created
the posts functionality. We create a comments model to handle the database interaction
for our comments and, of course, a comments controller to do the actual work. But we
don't need any views, since the comments will be displayed when we read a post.
All that doesn't necessarily have to be the case. We
could look at comments as a logical part of posts and decide to include them all in the
same controller, model, etc. For that matter, we could look at posts as comments
without a parent and do everything in one table. We could also write Blahg completely
in one PHP file if we wanted, but that doesn't mean it's a good idea. The idea here is
to break our application into distinct functional pieces.
The comments model is going to look just like the posts model, name changes aside. We
can do everything we need with the methods it inherits from Zend_Db_Table. And as
mentioned, we don't need any comment-specific views, but we need to modify the posts
read view to output any comments and include the form to let people post comments.
This form will point to the write action of the comments controller.
The comments controller will look like a slimmed-down version of the posts controller,
since all we need is a writeAction. But there will be an
important difference: After the data is written, we need to redirect the user to the
post he was reading when he submitted the comment. And speaking of that posts
controller, we need to modify the readAction to retrieve the
comments for each post and assign them to the view.
Find examples of how to do all this in the code archive. You
may have come up with a different solution. That's good. Now that we've added
comments with Zend, we can take a look at how to do it in symfony.
Adding comments with symfony
When it comes to adding comments with the symfony version of Blahg, we need to start in
our schema.yml (it should be in /column/protected/sf_column/config) and add
some definition for the comments table. We need to add the following lines.
Listing 3. Adding comments with symfony
comments :
_attributes: {phpName: Comment }
id:
post_id:
text: longvarchar
created: timestamp
|
As before, if you copied and pasted that text, make sure comments is indented two
spaces and that the elements under comments are indented four spaces. Whitespace is
important.
Note: Like CakePHP, symfony will perform some magic for you on the created and
modified columns if you name them created_at and modified_at, respectively. In this case, that magic is being picked
up by the MySQL database, instead.
Once we're done, we need to build the propel model and clear the cache. At the command
line, in /column/protected/sf_column, execute the following commands:
php /column/src/symfony/data/bin/symfony propel-build-model
php /column/src/symfony/data/bin/symfony clear-cache
|
Once we run these commands, you should see the files Comment.php and CommentPeer.php in
the /column/protected/sf_column/lib/model directory. Now that our models are sorted
out, we can whip up the rest of the comments functionality.
Note: This should have been mentioned in Part 2, but if you installed symfony
using the PHP Extension and Application Repository (PEAR), you can just run symfony propel-build-model
without calling PHP first or providing the full path to the binary. Not everyone has the
option of using the PEAR installation in their environment, which is why we described
the alternate installation instructions.
Now that your model is in place, we need to modify the actions.class.file.php in
/column/protected/sf_column/apps/blahg/modules/post/actions/ to add some selection
criteria for the comments and assign comments to the post. We also need to make some
modifications to the readSuccess.php file in the
/column/protected/sf_column/apps/blahg/modules/post/templates/ directory to output the
comments for a given post. Look at the files in the code archive for details on both of these.
Next, we need to init the comment module for Blahg. Run the
following command from the /column/protected/sf_column directory:
php /column/src/symfony/data/bin/symfony init-module blahg comment.
Next, we add the executeWrite action to the
actions.class.php in the
/column/protected/sf_column/apps/blahg/modules/comment/actions/ directory. This will
look similar to the executeWrite action from the post
module, except that after the write, we want to redirect back to the post for reading.
Finally, we need to modify the readSuccess.php file for the posts module, adding a form
that allows the reader to post his comments. All of this code is included in the code archive.
Adding comments with CakePHP
To add comments to the CakePHP version of Blahg, you will need a comments controller
(to handle writing out comments), but more importantly, you need to write a comment
model and define an association between comment and post. Once you specify that a post
"has many" comments and a comment "belongs to" a single post, the same query that
retrieves the post for reading will retrieve the comments for that post. These are
called model associations in CakePHP.
If you look in the code archive for the updated CakePHP version of Blahg, we see a
fairly typical comments controller (comments_controller.php), but with a significant
difference from your posts controller: The class variable autoRender set to false. This allows you to use actions from the
controller without views. Not setting autoRender to false
will result in "missing view" errors when you submit comments. Also in the code archive
are the new comment model and changes to the post model to reflect the model
association. As you can see, when you get your model associations straightened out,
CakePHP will do a lot of the heavy lifting for you.
Exceptions to the rule
So far, Blahg has been written to fit with each framework, taking advantage of the
strengths offered by each framework. That's pretty easy. It works great as long as the
framework we're working with can account for everything our application needs to do.
But what if an application doesn't fit within the framework?
If you ever find yourself in this predicament, the first question you should ask
yourself, even if you've already thought it through once, is this: Why am I using a
framework that doesn't let me define my application? The answer could be one of
several. Perhaps the framework is pre-existing and replacement is out of the question.
The framework may have been the only one your bosses would authorize. Or it could
simply be that your application is different and none of the frameworks on the market
really fits your model. If that's the case, before you start trying to force your
square peg of an application into some differently shaped framework, make sure your
application makes sense and is structured in a meaningful way.
Are you sure it needs to work that way? If so, that's good. Let's talk about extending the frameworks.
The problem
You can post items in Blahg, and now readers have an opportunity to respond to what
you've said. But it would really be nice to know at a glance what your Blahg is
talking about. You could give it a snazzy title that tries to encapsulate the topics
you intend to cover, like "World of Spatulas" or "Polka for the Rest of Us," but you
don't have any control over what your readers might be talking about in their posts.
And sometimes you might feel like talking about something else. Or maybe you might
just be a math geek and you want some numbers.
A possible solution to this might be something that takes incoming content, builds up a
count of words used, and gives you a neat little list of the most common words in your
Blahg. The problem: Where do you do that? You could argue it's a task to be
accomplished in the Model layer, since you're categorizing the data. You could also
argue that it's a task for the Controller, since you need to do some analysis, and
analysis implies logic. And if you want to see these numbers, won't you need something
in the View? But the user won't ever actually visit a words controller. How do you
extend the framework to do this?
To highlight the basic approach, we need a table to hold words (let's call that table
"words"). This table will hold a word and the number of times that word has occurred.
We might use the syntax in Listing 4 to define this table.
Listing 4. Table to hold words
CREATE TABLE 'words' (
'id' INT( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
'word' varchar(255) NOT NULL,
'occurrences' INT( 10 ) NOT NULL
) ENGINE = MYISAM ;
|
The code to do the word analysis will need to:
- Strip any HTML (which you should be doing anyway)
- Strip out nonalpha characters that are not whitespace (which messes up apostrophes, but it's a good place to start)
- Replace multiple whitespace characters with single whitespace characters
- Lowercase everything
- Explode on whitespace
- Generate word counts from the resulting array
- Update the words table with new counts, adding rows for new words and updating rows for words already there
Don't worry if that sounds like a lot. It will come together as MVC as you write the
code. What you will end up with in the end is, essentially, a Model to handle the
words table, a View that lets you see the word counts, and a Controller to perform some logic.
Adding word stats in Zend
If all we wanted to do was compile word counts without displaying them, we could
probably solve this problem using Zend_Controller_Plugin_Abstract. But since we want to actually be
able to view the word counts, it probably makes more sense to build a words module with
a Model and a Controller. The Model will be fairly basic, like others we've done for
Zend. The Controller will have an indexAction method that
will retrieve and display the word counts, but it will also need a method to process
the text: processText. This method does not end in
Action. This is intentional. The index view is also very
straightforward: It needs to iterate the list of words and dump them to screen.
The code for indexAction is fairly simple. Just get the
words, order by count (descending). But the code for the processText method has to do a little more. It needs to
strip the text, break it into an array, and count the occurrences of each word. Then
the code needs to get the words from the words table and put them into a list. If a
word is in the master list, we need to update the row with a new count. If a word is
new, we need to insert a row with that word. It's all fairly straightforward. You can
see all the code in the code archive.
To use the processText method in the posts and comments
controllers, we first need to require the WordController.php file. In the writeAction, we need to create an instance of the WordController. When we do this, we need to pass in Request and Response objects. Then we
just call the processText method as if it were a method on
any old object. For posts, don't forget to pass in the title and the text.
Tip: If we pass in the text after we passed it through
the StripTags filter, we don't have to strip tags later.
It might look something like this:
$wc = new WordController($this->_request, $this->_response);
$wc->processText($data['title'] . ' ' . $data['text']);
|
Check the code archive for details. Once we have everything in place and we modify the
posts and comments controllers, we should be able to enter some posts and comments, and
watch the word counts update at http://localhost/zend/word.
Note: You would probably write some of this code a bit differently in a
high-volume setup. The intent here isn't to show you how to do awesome text-parsing
word count-stats analysis, but to use it as an example in the context of trying to do
something the frameworks handle awkwardly.
Adding word stats In symfony
As you might have guessed, the first thing we need to do is add the definition of the
words table to the schema.yml configuration file. Keeping in mind the proper
indentation, the definition should look like Listing 5.
Listing 5. Adding the definition of the words table to the schema.yml file
words
_attributes: {phpName: Word }
id:
word: varchar(255)
occurrences: integer
|
What do we do next? That's right — we need to build the model and clear
the cache, exactly like we did when we added comments above. Then we need to init a word module. Make sure to run all those commands from the
/column/protected/sf_column directory.
Go ahead and do the easy part first. We know that the word module is going to do only
one thing that's user-accessible: show the list of words in the words table. You can
probably figure out what the executeIndex action and indexSuccess templates will look like by looking at the posts module.
Since the easy stuff is out of the way, we need to get the text from posts and comments
parsed, counted, and so forth. One way to do this would be to create an action called
executeParsetext in the actions.class.php for the word
module and forward to that action after a post or comment has been saved. Before doing
that, we should set an ID to hold the post ID, so we know where to return. In the posts
module's actions.class.php it might look like this:
$this->id = $post->getId();
$this->forward('word', 'Parsetext');
|
Once we execute the forward to the other module, we need to extract the variables
passed. This is actually a little tricky. The code to get to the variables looks
something like this:
$vars =
$this->request->getContext()->getActionStack()->getFirstEntry()->\
getActionInstance()->getVarHolder()->getAll(); |
Note: It really seems like there should be an easier way to do this. Another
technique is to set a value in the flashMessage and forward
it along, but that doesn't seem much better. Feel free to submit feedback if there's a more elegant way to do this.
Once you get to the variables, break up the text as before and use the word model to
save appropriate rows. To use this one action to parse text for posts and comments,
make sure to init the title variable to something. Once we're
done, we can redirect back to the posts module, and since we kept the ID of the post, we can go straight to reading it.
All of this is included in the code archive. Take some time to review it before moving
on to CakePHP.
Adding word stats in CakePHP
In CakePHP, we implement this functionality in two parts. We build what's called a
component, which is a sort of controller helper, and this component will process the
text. Then we make a standard model, controller, and view for words to view the list of
words from the database. The posts and comments controllers will use the words model to
save the word counts after they've been processed.
To create the WordcountComponent, create a the file called wordcount.php in the
/column/protected/cakephp/app/controllers/components directory. Define the class
WordcountComponent as an extension of object and create the method processText
using the now-familiar code to strip, split, and count the text into an array. This method should return the array of counted words.
Using what we already know about CakePHP, create a basic Model, View, and Controller
that will get the content of the words table and display them at the index action. No other actions should be necessary.
In our posts and comments controllers, we need to add a
couple lines to specify that we are using the word model and our new component:
var $uses = array ('Post', 'Word');
var $components = array('Wordcount');
|
After we have all that in place, we need to add the code to the write actions in both
controllers to strip the tags, pass in the text to the WordcountComponent, get the content of the words table, iterate the
array of counted words returned by the WordcountComponent,
and do the appropriate saves. By now, you should have a pretty good picture of how all
that will fit together, but as usual, the code to do all of this is included in the code archive.
Summary
No matter how much work is put into a framework, there's just no way to make one
framework that can handle every possible application a PHP developer can concoct. The
more rigid a framework is, the less it will flex to deal with exceptions to the rules.
Very early in the process of choosing a framework, try to see how the framework will
handle your unique needs. Forget trying out the Create, Read, Update, Delete (CRUD). Most frameworks handle that well
enough. It's the framework's ability to bend to your needs that will show you its
strength. You may need to bend your thinking to meet halfway, but frameworks that are
overly rigid will break when you require versatility.
See if you can come up with another unique problem that the frameworks won't like.
Then dive into the documentation and see if you can solve the problem. Or, if you're
having trouble thinking of a problem, come up with a good way to omit words from the
word-count process that will be too common to be useful, such as articles, prepositions,
pronouns, etc.
In Part 4, we use Ajax and see how to use
native code and third-party libraries, specifically how each framework behaves and
accepts specific popular libraries.
Download | Description | Name | Size | Download method |
|---|
| Part 3 sample code | os-php-fwk3.source.zip | 21KB | HTTP |
|---|
Resources Learn
-
For a good tutorial series on using the Zend Framework, read "Understanding
the Zend Framework, Part 1."
-
Another good Zend tutorial is "Getting
Started with the Zend Framework."
-
Check out the "My first symfony project"
tutorial.
-
Get the CakePHP Manual (Note: The manual is
written to CakePHP V1.1. There may be some variance as you are using CakePHP V1.2).
-
PHP.net is the central resource for PHP developers.
-
Check out the "Recommended PHP reading list."
-
Browse all the PHP content on developerWorks.
-
Expand your PHP skills by checking out IBM developerWorks' PHP project resources.
-
To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
-
Using a database with PHP? Check out the Zend Core for
IBM, a seamless, out-of-the-box, easy-to-install PHP development and production environment that supports IBM DB2 V9.
-
Stay current with developerWorks' Technical events and webcasts.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
-
Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
Get products and technologies
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
-
Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
About the author  | |  | Duane O'Brien has been a technological Swiss Army knife since the Oregon Trail was text only. His favorite color is sushi. He has never been to the moon. |
Rate this page
|