PHP frameworks, Part 5: Integrating external tasks

Scheduling framework-created tasks is easy and safe, once you know how

A short few years ago, a common criticism about PHP was that it did not support 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. In this article, you will integrate external tasks, creating a simple task that can be called using the scheduler cron.

Share:

Duane O'Brien, PHP developer, Freelance

Photograph of Duane O'BrienDuane 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.



19 February 2008

Also available in Russian Japanese Vietnamese

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. Part 3 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

Whenever an application interfaces with a database, it's not uncommon to find yourself needing to set up some automated tasks. Typically, these tasks are basic database management-oriented, such as table pruning, expiring accounts, etc. Historically speaking, it's common for these tasks to be written in Perl. While Perl is an excellent language, unless it is bringing something to the table that you don't already get from PHP, it makes more sense to write your automated tasks and your application with one language. It's easier to support, you can share code, and you don't get your syntax confused when switching back and forth.

In that spirit, since you've written Blahg in a framework, it would be awfully nice if you could continue to use the framework to execute common automated tasks. You can use the common structure that is already established, you can use methods you have already created, and you maintain that database independence you get from using a model instead of writing database interface code for a specific database. If you are going to use a framework, it makes sense to use it across the board.

In this article, you will look at writing an automated task for Blahg that prunes any posts older than 30 days. You will be able to call this task from the command line, enabling you to automate the task using a scheduler like cron. You'll build the application in each framework, getting a sense for how Zend, symfony, and CakePHP handle automated tasks differently.

You should have already gone through Parts 1-4, which cover installation, prerequisites, the initial application build and extension, and Ajax for each framework. If you haven't, you should do so now.

Note: Each article includes a ZIP file containing all the code built and discussed for the article. This file will be referred to throughout the article series as the code archive.


Let's get this out of the way now

Given the structure of the frameworks, there is a short path to creating an automated task that you can call from cron: Make the task an action in an existing (or new) controller and call the action using wget. There's absolutely no reason this will not work, and if you are looking for a down-and-dirty and, above all, insecure way of executing tasks, using wget would probably be fine.

But the word "insecure" in that sentence should raise some flags. By writing your automated tasks this way, you make them available to the world. Even if you don't link to the action in any way, use a controller named Mxyzptlk, or require a 40-character unique string that must be passed to the action before execution, the action is still available for the world to execute. Security through obscurity is no security at all. Since many automated tasks can tend to be database-intensive, you do not want these actions executed at random.

So, while there is a short path to automating your tasks you are ideally looking for a more secure approach. Each example below will show you one way to do things for each framework. There is always more than one way to do things. Once you get through the article, you may want to experiment on your own or do some additional research. See how other people have done it.

Also, do yourself a favor: back up your tables before you proceed. You will be deleting some records, and it will be helpful to be able to restore the tables to a default state for later testing.

Enough prologue. Let's dive in.


External tasks in the Zend Framework

Due to the pick-and-choose nature of the Zend Framework, creating an automated task using the framework allows for some flexibility in deciding what you use and don't use. Because you are not going to be accessing the Web application through the existing front controller, which services Web requests, you should create an additional controller to control script execution. This controller will look similar to the front controller, in that you will be registering the autoloader and defining the base adapter for the model. But an important difference may be in your PHP installation or configuration.

Compared to invoking PHP from the Web server, command-line PHP typically is executed using a different Server Application Programming Interface (SAPI). Depending on your installation and configuration, this may mean that the include path is different. For this reason, you should set the include_path ini setting to include the /column/include directory where you installed the Zend Framework files.

Note: Remember that the way you define the include_path will be different depending on your OS. The example below is for Linux®.

Create a new directory called /column/protected/zend/scripts. This directory will be used to hold your scripts controller and any additional script-related files you create. Since you are only creating one task at this time, start by creating the file prune.php in the new scripts directory. The top half of this file will contain the primary code for your scripts controller.

Listing 1. Code for your scripts controller
<?php

ini_set('include_path', ini_get('include_path') . ':/column/include');

require_once("Zend/Loader.php");
Zend_Loader::registerAutoload();

$params = array(
        'host'          => 'localhost',
        'username'      => 'frameworks',
        'password'      => 'fwpw',
        'dbname'        => 'zend'
);

$db = Zend_Db::factory('PDO_MYSQL', $params);

Zend_Db_Table::setDefaultAdapter($db);

The rest of this file will contain the simple task you want to execute. Include and instantiate the posts model, create a where clause and run a standard delete query.

Listing 2. Task to execute
require_once("/column/protected/zend/models/Posts.php");

$posts = new Posts();

$where = $posts->getAdapter()->quoteInto('modified  < ?', 
    date('Y-m-d H:i:s', strtotime('-30 days')));

$posts->delete($where);

?>

Once this is complete, you can execute this script at the command line: php /column/protected/zend/scripts/prune.php.

If you can call it from the command line, you can schedule it, batch it, or cron it. A crontab entry to execute this script at midnight could look like this: 00 00 * * * php /column/protected/zend/scripts/prune.php.

Once you've built your scripts controller, you have a whole host of options for executing scripts using the Zend Framework. You can use the same models the rest of your application uses, create complex script objects where required, and more.

Now you have a good handle on how to do a cron job for Zend. Next, let's look at doing the same thing in symfony.


External tasks in symfony

When you wanted to execute a command-line script in the Zend Framework, you created a scripts controller that would be used instead of your front controller. You will do much the same thing with symfony.

Create a directory to hold your symfony Blahg scripts — /column/protected/sf_column/apps/blahg/scripts/ — and create a file called prune.php to be your new scripts controller. The first half of this file will look much like the symfony front controller you created. You will define the required constants and require the config file. The difference in this case is in how you get the symfony instance. In the front controller, you told symfony to look for the controller and dispatch the request. For the script controller, this is not the case.

Listing 3. Defining the required constants and requiring the config file
<?php

define('SF_ROOT_DIR',    '/column/protected/sf_column');
define('SF_APP',         'blahg');
define('SF_ENVIRONMENT', 'prod');
define('SF_DEBUG',       false);

require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR
.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');

sfContext::getInstance();

Next, you'll create a basic Criteria to pass to the post model and run the delete.

Listing 4. Creating the basic Criteria
$c = new Criteria();

$c->add(PostPeer::MODIFIED, date("Y-m-d H:i:s", 
          strtotime("-45 days")), Criteria::LESS_THAN);
$posts = PostPeer::doDelete($c);

Save the file. That's all there is to it. Now you can execute the script at the command line, much like you did in the Zend Framework: php /column/protected/sf_column/apps/blahg/scripts/prune.php.

Calling this task from a scheduler would look just like it did for the Zend Framework (specifying the symfony script rather than the Zend script, of course). For cron, it would look like this: 00 00 * * * php /column/protected/sf_column/apps/blahg/scripts/prune.php.

Finally, let's look at how you would do the same task in CakePHP.


External tasks in CakePHP

The Cake console is a new feature in CakePHP V1.2 that provides a command-line interface to the Cake framework. To create your own command-line tasks, you create what is known as a shell. A shell looks much like the controllers you already created.

Start by creating the file prune.php in the /column/protected/cakephp/app/vendors/shells directory. This is your new shell called prune and will be used to delete any posts older than 30 days. Define a new class, PruneShells, that extends the shell class. Since you are going to be deleting posts, the shell will need to use the post model, which you can specify using the $uses variable. By default, when you tell Cake to execute a shell, and you pass no specific action, Cake looks for a method called main and, if found, executes the method. At this point, the empty shell would look like Listing 5.

Listing 5. Defining a new PruneShells class
<?php

class PruneShell extends Shell {
    var $uses = array('Post');

    function main() {
    }
}
?>

Now all you need to do is add code to the main method to delete any posts older than 30 days.

Listing 6. Deleting posts older than 30 days
$conditions = array (
    "Post.modified" => "< " . date("Y-m-d H:i:s", strtotime("-30 days"))
);
$this->Post->deleteAll($conditions);

To execute this script from the command line, you tell Cake that you want to run the prune shell. Since all the code is in the main method, it will execute by default. You should also tell Cake what the app directory is. This isn't necessary if you are running the command from the app directory, but a cron job won't be executing from the right directory unless you tell it to: /column/protected/cakephp/cake/console/cake prune -app /column/protected/cakephp/app/.

Scheduling a cron job to run at midnight that executes this shell would look like this: 00 00 * * * /column/protected/cakephp/cake/console/cake prune -app /column/protected/cakephp/app/.

Note: If you add the /column/protected/cakephp/cake/console directory to your PATH, you don't have to specify the full path, which makes working with the console much easier. Specifying the full path in your batch or cron job will help ensure proper execution regardless of the user that executes the job.

Now that you can call it from the command line, you know you can batch it, cron it, or schedule it.


Summary

Writing automated tasks for your application has obvious benefits. You should be able to write these automated tasks in the framework of your choosing. By writing your tasks using your framework, you apply a consistent approach throughout your code and take advantage of the existing structure you already have in place.

As you may have noticed while testing out the prune scripts, comments and ranks associated with the posts may not have been orphaned when the pruned posts were deleted. Taking what you have already learned about the frameworks, modify each prune script so that the related comments and ranks are deleted during pruning. Try to use each frameworks particular way of interacting with related tables.


Download

DescriptionNameSize
Part 5 sample codeos-php-fwk5-column_part5.zip32KB

Resources

Learn

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

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=288762
ArticleTitle=PHP frameworks, Part 5: Integrating external tasks
publish-date=02192008