Web Development Articles

July 20, 2007 – 6:14pm Symfony: path to upload directory, other sf_* config values

sf_upload_dir, sf_upload_dir_name, sf_data_dir… I know there are a lot of configuration values available throughout the symfony code, but I don’t use them enough to remember what they are exactly. The Symfony Upload File documention helps describe which paths you need for for file uploads. In your templates/view, you will want to use:

sfConfig::get('sf_upload_dir_name')

This will usually be the string “uploads” unless you changed the setting in your configuation. When you need the *full path* to the file on your web server, say to move or save the uploaded file, then you use:

sfConfig::get('sf_upload_dir')

To make things easier, usually I will use those configuration settings in conjunction with a custom setting for the type of media I am uploading. Say for example I have a “Person” table that has a column named “photo”. This column stores a file name of a person’s picture. I want to store all of these photos in web/uploads/photos. First, I’ll add a setting to my app.yml file:

all:
  dir:
    photo:    "photos"
    doc:       "docs"
    logo:      "logos"

Then in my lib/model/Person.php, I’ll add a method to give me the web path:

public function getPhotoPath()
{
  return $this->getPhoto() ? '/' . sfConfig::get('sf_upload_dir_name') . '/' . sfConfig::get('app_dir_photo') . '/' . $this->getPhoto() : null;
}

May seem like a lot of code, but now anytime I want to display the person’s picture in a template I can use:

<?php image_tag($person->getPhotoPath()) ?>

If I ever decide to move all the stored photos into another directory, perhaps because it’s growing too large and we want them in subfolders based on username or something, then all we have to do is change it in one place and we can be certain no existing templates will break.

If instead you wanted to change the actual ‘uploads’ path, then you can override the symfony parameter in your project’s config/config.php file (note this is copied from the symfony documentation page linked to above):

sfConfig::add(array(
  'sf_upload_dir_name'  => $sf_upload_dir_name = 'uploads',
  'sf_upload_dir'       => sfConfig::get('sf_root_dir').DIRECTORY_SEPARATOR.sfConfig::get('sf_web_dir_name').DIRECTORY_SEPARATOR.$sf_upload_dir_name,
));

As a final note for the curious, if you look in your symfony directory (the actual symfony code base, not your project directory), you can take a look at all of the sfConfig values in config/constants.php. Here are a few variables and their default values that might come in handy:

$sf_root_dir    = sfConfig::get('sf_root_dir');
$sf_app         = sfConfig::get('sf_app');
$sf_environment = sfConfig::get('sf_environment');

$sf_bin_dir_name     = 'batch',
$sf_cache_dir_name   = 'cache',
$sf_log_dir_name     = 'log',
$sf_lib_dir_name     = 'lib',
$sf_web_dir_name     = 'web',
$sf_upload_dir_name  = 'uploads',
$sf_data_dir_name    = 'data',
$sf_config_dir_name  = 'config',
$sf_apps_dir_name    = 'apps',
$sf_test_dir_name    = 'test',
$sf_doc_dir_name     = 'doc',
$sf_plugins_dir_name = 'plugins',
Posted in  Web Development   |     |  1 Comment »   |  delicious  Digg

July 19, 2007 – 2:36am Javascript Array Merge: array_merge

Using protoype.js I discovered a quick way to merge to arrays in javascript:

function array_merge(one, two) {
  one.push(two);
  return one.flatten();
}

This definitely beats the alternative of iterating through the second array by hand and adding it to the first. Any comments or suggestions are welcome.

Posted in  Web Development   |     |  3 Comments »   |  delicious  Digg

June 18, 2007 – 12:15pm Linking between Apps within Symfony

There has been some recent discussion on the symfony mailing list about cross-linking between apps within the same project. I have managed to avoid having to use complex routing rules and instead have gotten away with adding absolute URLS to my app’s settings.yml file, and then using these addresses within my templates.

First, add appropriate URLs in the project’s config/app.yml file. As there are different settings used depending on the environment, you can put the full URL here and have it automatically use the right setting.

prod:
  url:
   frontend:  http://public.mysymfonyapp.com
    backend:   http://private.mysymfonyapp.com
dev:
  url:
   frontend:  http://myappdev/frontend.php
   backend:  http://myappdev/backend.php

Then, in your template, whenever you need to link to the other app, use these settings:

<?php echo link_to('Admin Site', sfConfig::get('app_url_backend')) ?>
or
<?php echo link_to('Public Site', sfConfig::get('app_url_frontend')) ?>
Posted in  Web Development   |     |  2 Comments »   |  delicious  Digg

June 17, 2007 – 10:21pm Symfony Resources

Even after working with Symfony for over a year, we still have to refer back to various resources to help us along the way. This is a list of useful resources for a symfony developer, including links to the sometimes hard to find Creole API, Propel API, and Propel docs.

Posted in  Web Development   |     |  No Comments »   |  delicious  Digg

June 13, 2007 – 2:15pm svn export and zip

Sometimes it’s necessary to make a copy of a project stored in an SVN repository. If you just copy the directory manually, you will also be copying those sneaky .svn files that exist in every directory in the project. Instead you should use the svn export command. This will make a new copy of all of the files in the respository than you can zip up and send off to whomever you please.

svn export file:///path/to/your/repository my_export

If you want to zip this baby up:

zip -r my_export.zip my_export

If you wanted to get really creative, and tag this export as a release, you could something like this:

svn copy file:///path/to/repos/trunk file:///path/to/repos/tags/1.0release -m "tagging version 1.0 release"
svn export file:///path/to/repos/tags/1.0release 1.0release
# for a zip:
zip -r 1.0release.zip 1.0release/
# for a tgz
tar -czvf 1.0release.tgz 1.0release.zip/*

References:

  • http://svnbook.red-bean.com/en/1.1/re10.html
Posted in  Web Development   |     |  1 Comment »   |  delicious  Digg

June 12, 2007 – 4:12pm Symfony exceptions, logging, and debugging

Since I always forget, here is a quick reference for throwing exceptions and adding log/debug messages throughout your symfony project.

Exceptions
This is how you can throw a new symfony exception.

throw new sfException('An error occurred. Please go back and try again.');  

Logging

Within an action, this is how you can log a message:

<?php use_helper('Debug') ?>
<?php if ($problem): ?>
  <?php echo log_message('{mainActions} been there', 'err') ?>
<?php endif ?>

And elsewhere within the code (model, etc):

sfContext::getInstance()->getLogger()->info($message);
sfContext::getInstance()->getLogger()->err($message);

Debugging

// in an action
$this->debugMessage($message);
// in a template
<?php echo debug_message($message) ?>

Reference: Symfony Docs: Debugging

Posted in  Web Development   |     |  No Comments »   |  delicious  Digg

June 12, 2007 – 12:06pm Propel Queries using Custom SQL, Peer Classes, and Criterion Objects

Propel provides many ways to generate queries. Here are a few of the more common methods.

Perhaps the easiest way for newcomers to Propel are custom queries written using standard SQL.

$con = Propel::getConnection(DATABASE_NAME);
$sql = "SELECT books.* FROM books
    WHERE NOT EXISTS (SELECT id FROM review WHERE book_id = book.id)";
$stmt = $con->createStatement();
$rs = $stmt->executeQuery($sql, ResultSet::FETCHMODE_NUM);
$books = BookPeer::populateObjects($rs);

Another method would be to use the peer class to gain some more flexibility in case down the line you decide to change your column names. For this we will use the PrepareStatement() method. This example also returns specific data from the query rather than entire propel objects.

public function getTotals($banner_id = NULL, $letter = NULL) {
	$con = Propel::getConnection();

	$sql = "SELECT SUM(".LetterBannerPeer::VIEWS.") AS view_total,
		SUM(".LetterBannerPeer::CLICKS.") AS click_total
		FROM ".LetterBannerPeer::TABLE_NAME."
		WHERE ".LetterBannerPeer::LETTER." = ?
		AND ".LetterBannerPeer::REGION_ID." = ?
		GROUP BY (".LetterBannerPeer::LETTER.")";

	$stmt = $con->PrepareStatement($sql);
	$stmt->setString(1, $letter);
	$stmt->setInt(2, REGION_ID);

	$rs = $stmt->executeQuery();
	while ($rs->next()) {
		$totals['views'] = $rs->getInt('view_total');
		$totals['clicks'] = $rs->getInt('click_total');
	}
	return $totals;
}

Notice how anytime you want to insert actual values into the query, you put in a ? (question mark) as a place holder which is then automatically escaped for the query by calling the $stmt->setString($position, $value) function.

For our third example we will use the Criterion Object. This function returns a propel object.

public static function getResponseTypes($message_type_id)
{
	$c = new Criteria();
	$c->addJoin(MessageTypePeer::ID,
		MessageTypeResponsePeer::RESPONSE_TYPE_ID, Criteria::LEFT_JOIN);
	// think of a Criterion object as one 'XX=YY' within the WHERE clause
	$cton1 = $c->getNewCriterion(
		MessageTypeResponsePeer::MESSAGE_TYPE_ID, $message_type_id
	);
	$cton2 = $c->getNewCriterion(
		MessageTypePeer::NAME, 'general'
	);
	$cton1->addOr($cton2);  // combine them into one (XX=YY OR AA=BB) clause
	$c->add($cton1); 		// add to Criteria

	return MessageTypePeer::doSelect($c);
}

The Criteria object provides some other useful functions for generating queries. This is a great example of how to use addAsColumn(), addGroupByColumn(), and addDescendingOrderByColumn().

$c = new Criteria();
$c->addJoin(BookPeer::ID, FavoriteBookPeer::BOOK_ID, Criteria::LEFT_JOIN);
$c->addAsColumn('cnt', 'COUNT('.FavoriteBookPeer::BOOK_ID.')');
$c->addGroupByColumn(BookPeer::ID);
$c->addDescendingOrderByColumn('cnt');
$book = BookPeer::doSelect($c);

If you want to use a criteria object but only select certain columns, you can do something like this:

$c = new Criteria();
$c->clearSelectColumns()
$c->addSelectColumn(self::PAYABLE_TO);
$c->add(self::ID, $id);

$rs = self::doSelectRS($c);
$payable_to = false;
if ($rs->next()) {
  $payable_to = $rs->getInt(1);
}

If you want to do a subselect, you can use Criteria::CUSTOM.

$c = new Criteria();
$subSelect = "users.user_type_id IN (
    SELECT
          user_type.ID
    FROM
          user_type
    WHERE
         user_type.name = 'public'
    )";
$c->add(UserPeer::USER_TYPE_ID, $subSelect, Criteria::CUSTOM);
$publicUsers = UserPeer::doSelect($c);

You of course could rewrite that query using JOINS if you thought that was too messy.

This custom criterion type is also useful when you want to compare two fields from the same table.

$c = new Criteria();
$c->add(MyTablePeer::COL1, MyTablePeer::COL1.'>='.MyTablePeer::COL2, Criteria::CUSTOM);

Take a look at snippets 75 and 53 for more information.

Posted in  Web Development   |     |  8 Comments »   |  delicious  Digg

June 10, 2007 – 11:53pm Turn off php magic_quotes_gpc within .htaccess or php.ini

A lot of shared hosts will have magic quotes turned on by default. This can create some extra overhead depending on your application. Symfony apps prefer to have this turned off. Here’s how you can do it using either an .htaccess or php.ini file in your web root directory.

#.htaccess
php_flag magic_quotes_gpc off

# php.ini
magic_quotes_gpc=off
Posted in  Web Development   |     |  No Comments »   |  delicious  Digg

June 10, 2007 – 3:59pm Rsync to the rescue: Copying files from server to server

We recently had to move a client’s web site to a new host. There is about 600 MB of images, video clips, and other files that we needed to transfer between hosts. Rather than FTP the files down from the old host and then re-copy them up to the new host, we used rsync to copy the files directly between the two hosts. To do this, you need shell access to both servers.

Login to the new host. Then at the shell prompt use the following command:

 rsync -avc -e ssh [user]@[oldhost]:[path] [new path]

The -avc flags are for archive mode (recursive), verbose, and checksum. -e is to specify the remote shell.

Posted in  Web Development   |  2 Comments »   |  delicious  Digg

May 26, 2007 – 2:59am Count your SLOC: Source Lines of Code

Although this number may mean nothing at all, counting the total number of Source Lines of Code (SLOC) can be fun. Here is how you can tally up all the lines of PHP you have in a project from the command line:

find . -regex '.*\\.\\(php\\)' -print0 | xargs -0 cat | wc -l

You can read more about this on wikipedia.

Posted in  Web Development   |     |  No Comments »   |  delicious  Digg