Archive

Programming

I am developing a Symfony 2.0 web app. I wanted to get my app running on the production server as soon as possible so that I know that it will work there. I couldn’t find an article on the web that presents a straight forward howto for deploying the production environment of a Symfony 2.0 web app to a remote production server. After a bit of hacking, I figured out how to do it. This article details the quick and dirty method I used to get a Symfony 2.0 web app onto a production server.

It should be mentioned that there are tools which try to automate the deployment process such as capifony, though I personally couldn’t get capifony to work for me. Besides, I want to understand the process by which a Symfony 2.0 app can be manually migrated from the development environment on my machine to the production environment on a remote server before I start relying on tools to automate the process for me.

Development Environment

System: Mac OS X 10.6
HTTP Server: Apache 2.2.17 running mod_php
PHP: 5.3.4
Symfony2 Project dir: /Library/WebServer/Documents/symfony2

This is my local machine where I develop my web app.

Remote Production Server

System: Linux 2.6.18-374.3.1.el5.lve0.8.44 (results from uname -a on my account)
HTTP Server: Apache 2.2.21
PHP: 5.3.10 (running as a cgi process)
Symfony2 Project dir: /home/james/symfony2

This is a shared hosting account at Arvixe web hosting.

I can access my shared hosting account via ssh and run commands from a bash shell. Before I began, I created a ~/symfony2 dir on the remote server where my Symfony2 webapp will reside. I set the root dir of my domain name to point to ~/symfony2/web. Getting httpd to do this is beyond the scope of this article.

In this article, all relative paths are relative to the main symfony2 project dir. For example, web/ refers to /Library/WebServer/Documents/symfony2/web on my local machine and to ~/symfony2/web on my remote server.

Deploying the project

Check the remote environment

Note: While the following information specifically mentions my shared hosting provider, it should be relevant everywhere. If you are the admin of your own server, you are of course responsible for installing and configuring php 5.3 yourself.

You may find that your remote host does not yet have php 5.3.2 installed. This is a requirement for a Symfony2 project and you should have your site admin install it for you, if it isn’t hiding somewhere on your remote account already. The default command line (cli) php on Arvixe is PHP 5.2.17.

$ php --version
PHP 5.2.17 (cli) (built: Dec 22 2011 01:10:30)

You may have to do some digging for yourself in order to find where php53 lives on your remote host. Here is a hint on how to find it:

$ find / -name "php53" -print

I setup an alias in my .profile file that points to the php 5.3 bin:

alias php="/usr/local/php53/bin/php"

Now when I type ‘php’ in my shell I see this:

$ php --version
PHP 5.3.10 (cli) (built: Feb  5 2012 13:35:22) 

After this, I uploaded the script in app/check.php and ran it

$ php check.php

to make sure that Symfony2 will actually run. Everything was OK.

Arvixe provides php 5.3 as an option for web applications, which must be run by modifying my .htaccess file in my web/ dir. See this article for more information. Your server will probably have a similar setup and a line to add to your .htaccess file. You can always verify which php version your web server uses by running the simple script on your remote server in your browser:

<?php phpinfo(); ?>

Transfer the project dir via rsync

I wanted to keep things as simple as possible, so I used rsync. I created a /Library/WebServer/Documents/symfony2/app/config/rsync_exclude.txt file whose content is:

*.txt
.git
deps
README.md
- app/cache/*
- app/logs/*
- web/.htaccess
- app/config/parameters.ini

I transfered the files using the rsync command on my local machine:

$ rsync -avz /Library/WebServer/Documents/symfony2 username@hostname:~/\
--exclude-from=/Library/WebServer/Documents/symfony2/app/config/\
rsync_exclude.txt

You could do something similar using sftp or even ftp. All you have to do is get your project directory onto your remote account. In my case, this means getting /Library/WebServer/Documents/symfony2 to ~/symfony2 .

Edit parameters.ini

I had to make changes to my app/config/parameters.ini file. As I am using Doctrine with a MySQL database backend, I needed to change my parameters to reflect those of the localhost. This is a pretty straight forward process and beyond the scope of this article to show you how to do this.

Create routing_prod.yml

I had to create a routing file for my production environment in app/config/. I copied “routing_dev.yml” to “routing_prod.yml” and took out any extraneous routes that I didn’t need for my production environment. You must be sure to remove the routes for:
_wdt
_profiler
_configuration

I also needed to add the following lines to my app/config/config_prod.yml file:

framework:
    router: { resource: "%kernel.root_dir%/config/routing_prod.yml" }

so that the new routing parameters will be used by my app.

Console commands

IT IS ESSENTIAL WHEN RUNNING THE CONSOLE COMMANDS THAT YOU USE THE COMMAND LINE OPTION:

--env=prod

IF THINGS AREN’T WORKING PROPERLY, DOUBLE CHECK THAT YOU HAVE USED THIS OPTION!

After I made the changes to my configuration files, I needed to run some console commands to get everything working. Make sure that when you are running the app/console program that you do it from your main symfony2 project dir on your remote account.

First, I had to publish my assets

$ php app/console assets:install --symlink web --env=prod

Then I created the database that I referenced in my parameters.ini file with the command:

$ php app/console doctrine:database:create --env=prod

Finally, I had to update my schema so that the tables were created in the database for my models

$ php app/console doctrine:schema:update --force --env=prod

I had to use the “–force” option because app/console is hesitant to create tables in the production environment database.

That is all! If I missed a crucial step… please let me know so that I can update this post. Otherwise, happy hacking and good luck developing your Symfony 2 apps!

Advertisements

Introduction

I have a web app written in Symfony 1.4 for which I have lime unit tests. lime is the built-in unit test framework that comes with the Symfony framework.

I have been running my unit tests ever so often in my project_root dir. I would like my file system to be continuously monitored so that whenever I edit files in my Symfony project, my lime unit tests are run automatically. If any unit test fails, I want to be notified instantly via Growl. After some googling, I found these blog posts related to the matter:

Autotesting with watchr, growl and PHPUnit

CakePHP: Autotest using Watchr

I take the above posts a step further in that I show how to launch watchr automatically whenever you login, using launchd.plists. I also further the watchr.rb script to notify the user whenever a model is modified that all unit tests have passed. If there is an error, the user is alerted to which tests have failed.

Requirements

Growl and growlnotify

Mac OS X 10.6 (Probably valid for OS X < 10.6)

Ruby (I’m using 1.8.7) and RubyGems

Optional Symfony 1.4
Not really required to follow along, however my final watchr.rb script
is used to parse lime unit tests output.

Demonstration

watchr

watchr can be installed via ruby gems

$ sudo gem install watchr
Password:
Successfully installed watchr-0.7
1 gem installed
Installing ri documentation for watchr-0.7...
Installing RDoc documentation for watchr-0.7...
$

watchr is run from the command line, with its single argument being
the ruby script that it runs. Below is a simple script which outputs
the name of the file that is modified

# watch is the DSL function provided by watchr
watch('lib/model/doctrine/(.*).php') { |m| code_changed(m[0]) }
def code_changed(file)
  puts("MODIFIED: #{file}")
end

Here, I run watchr from the command lind and modify a file in another
window

/Library/WebServer/Documents/project_root$ watchr watchr.rb 
MODIFIED: lib/model/doctrine/MyModel.class.php
_

lime unit tests

In this demonstration, I am using a simple MyModel.test.php file which contains the following code

<?php
/**
 * test for MyModel class
 *
 * @author James Borden
 */
require_once dirname(__FILE__).'/../../bootstrap/unit.php';
                              $t = new lime_test(1);
                              fakeFunction();
                              $t->is('1', '1', '1 == 1');
?>

When a test passes, it looks like this

When a test fails, it looks like this

If I add a a new test to a MyModel.test.php, but forget to update the amount of planned tests (i.e. update lime_test(n) to n total tests), I receive the following error.

Other times, the PHP compiler itself chokes on the test, printing
messages to STDERR. Here I add the fake function “fakeFunction()” to
MyModel.test, which results in a PHP Fatal Error

My watchr.rb

Below is the code for my new watchr.rb script. This script is given a set of files to monitor. If the files that are being monitored are edited, watchr will run each test file, parse any error messages that it may receive and send the error message via growl to the user.

Click below to view code. It is commented for the curious.

# Needed for displaying dates, used later on in the system call to growlnotify
require 'date'
# List of files to watch.
watch('lib/model/doctrine/(.*).php') { |m| code_changed(m[0]) }
watch('lib/(.*).php') { |m| code_changed(m[0]) }
watch('lib/form/(.*).php') { |m| code_changed(m[0]) }
watch('lib/form/doctrine/(.*).php') { |m| code_changed(m[0]) }
watch('lib/validator/(.*).php') { |m| code_changed(m[0]) }
watch('lib/widget/(.*).php') { |m| code_changed(m[0]) }
# what do when code is changed
def code_changed(file)
  # Array used to store the directorys that contain unit tests
  testDir = []
  testDir.push('test/unit')
  testDir.push('test/unit/model')
# Assume there won't be any errors
  error = false;
  # Go through all the test dir
  testDir.each do |test_dir|
    # select all *.php files from test dir
    test_dir_files = File.join("**",test_dir,"*.php")
    phpTestFiles = Dir.glob(test_dir_files)
    # For each PHP test file
    phpTestFiles.each do |phpTestFile|
      # run the system command php on each file
      if(!runCmdOnFile("php",phpTestFile))
      # The test had an error
        error = true;
      end
    end
  end
  # If there are no errors, let the user know
  if(!error)
    growl("All Passed")
  end
end
# command is run on a file and the output is parsed
def runCmdOnFile(cmd, file)
  if(cmd == "php")
  # we want to redirect stderr to stdout so that watchr can handle error messages
  # when the php interepreter itself barfs on our test code
  run_output = `#{cmd} #{file} 2>&1`
     if(checkMessage(run_output))
       return true
     else
       # Try to parse the message for errors
       errorMessage = parseError(run_output, file)
       growl(errorMessage)
       return false
     end
  end
end
# Simple check to see if the test passed
def checkMessage(run_output)
  if run_output.include?("# Looks like everything went fine.")
    return true
  else
    return false
  end
end
# Try to figure out what went wrong when the program was executed
def parseError(run_output, file)
  # Array to contain the error message
  errorMessage = []
  # First, note the name of the test that failed
  errorMessage.push("#{file}\n \n")
  output = []
  output = run_output.split("\n")
  run_output = output;
  # Look through the output for common errors.
  # This can be expanded as new problems arise
  run_output.each do |line|
    if(line =~ /# Looks like you planned/)
      errorMessage.push("You probably forgot to update lime_test.\n")
    end
    if(line =~ /not ok/)
      errorMessage.push("Test Failed\n")
    end
    # This is real bad, the php interpreter barfed on the code
    if( line =~ /PHP Fatal error/)
      errorMessage.push(line + "\n")
    end
  end
  return errorMessage;
end
# Function to send messages to user via growl
def growl(message)
  # Check to see if the message is a "All Passed" signal
  # and set the image accordingly
  if(message.include?("All Passed"))
    image = "~/.watchr/images/passed.png"
    # Set the passed flag to true
    passed = true
  else
    # There was some kind of error
    image = "~/.watchr/images/failed.png"
  end
  # Transform any messages that are strings into an array
  if(!message.kind_of?(Array))
    messageArray = []
    messageArray.push(message + "\n")
    message = messageArray;
  end
  # If the error output of test is too long,
  # growlnotify will break. Limit the message to the first 2
  message = message.first(2);
  message.push("\n")
  # Timestamp our message
  message.push(Time.now.asctime)
  # Where does your growlnotify live? "which growlnotify" will tell you
  growlnotify = '/usr/local/bin/growlnotify'
  # Options to pass to growlnotify
  if(passed)
    # Let's not make an "All Passed" signal sticky.. better to just reassure the
    # user everything is ok when a file is saved
    options = "-n Watchr -m '#{message}' --image #{image}"
  else
    # If there is an error, make it persist so that the user has to least acknowledge it
    options = "-s -n Watchr -m '#{message}' --image #{image}"
  end
  # Make the system call to growlnotify
  system("#{growlnotify} #{options}")
  return true
end

Here are the passed.png and failed.png that I used with growl.

Growl output

The new watchr.rb will check my tests and send output via growl.

Whenever I make changes to the files being watched and all tests passed, I like to be reassured everything is fine

If I add a new test to a MyModel.test.php, but forget to update the amount of planned tests (i.e. update lime_test(n) to n total tests), I receive the following error.

If I add a a new test to a MyModel.test.php, but forget to update the amount of planned tests (i.e. update lime_test(n) to n total tests), I receive the following error.

Finally, whenever PHP chokes completely on the file, growl outputs this

Creating a plist for launchd to automatically load watchr at login

If I had to manually run watchr every time that I code, I would eventually forget about running it altogether! I want watchr to always be there, checking over my code to make sure that all of my tests are still passing as I edit my project. For this, I use the built-in launchd daemon/agent manager program.

launchd is “the official” way to handle background programs (daemons) in Mac OS X. launchd works by loading xml-based plist files for every process. The plist file dictates how the process is handled by launchd. I wrote the following example watchr.plist to run watchr in the background automatically whenever I log in. watchr.plist lives in ~/Library/LaunchAgents

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" \
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <!-- Contains a unique string that identifies my plist to launchd.
       This key is required. -->
  <key>label</key>
    <string>watchr.plist</string>
    <!-- watchr is started in the same dir as the watchr.rb script -->
    <key>WorkingDirectory</key>
    <string>/Library/WebServer/Documents/project_root</string>
    <!-- Contains the arguments [to execvp()] used to launch the daemon.
    This key is required.  -->
    <key>ProgramArguments</key>
    <array>
      <string>/usr/bin/watchr</string>
      <string>/Library/WebServer/Documents/project_root/watchr.rb</string>
    </array>
    <!-- This plist is loaded whenever I log in, but the command is not
    automatically run. This key ensures that it is run whenever this plist file is loaded -->
    <key>RunAtLoad</key><true/>
</dict>
</plist>

I use the system command, launchctl, with the subcommands load and unload from the command line to manage the watchr background process. Here, I load the newly created watchr.plist and the watchr process starts automatically. This is due to the fact I have the RunAtLoad key set to true.

~/Library/LaunchAgents$ launchctl load watchr.plist
~/Library/LaunchAgents$ ps -wax | grep ruby | grep -v grep
49081 ??         0:00.20 /System/Library/Frameworks/Ruby.framework/Ver
sions/1.8/usr/bin/ruby /usr/bin/watchr
/Library/WebServer/Documents/project_root/watchr.rb
~/Library/LaunchAgents$

I can also unload watchr.plist, and the process is stopped automatically.

~/Library/LaunchAgents$ launchctl unload watchr.plist
~/Library/LaunchAgents$ ps -wax | grep ruby | grep -v grep
~/Library/LaunchAgents$

If I had not specified RunAtLoad key to be true, the start and stop subcommands could have been used to start and stop the watchr process manually. Because watchr.plist lives in my personal ~/Library/LaunchAgents dir, it will be loaded whenever I login and turned off whenever I log out. About as automatic as it gets!

Conclusion

I have demonstrated how to use watchr to monitor files and run the corresponding lime unit tests whenever the files are changed. I have shown how to use Growl to notify the user of the tests’ status. I have also shown how to use launchd to automatically run watchr when the
user logs in. Thus, I have shown how to setup a continuous testing environment on the Mac for Symfony 1.4 projects.

It is my hope that I have given the reader enough knowledge to implement this system for their own unit tests. You certainly need not be limited to only lime unit tests. My watchr.rb script can be used as a template for your own projects. Happy coding and may all of your tests continually pass!

Update: You can find a github repository with this code here.