Symfony Articles

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 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

May 16, 2007 – 2:02pm Symfony Cookbook

There are a few additional sections that didn’t make it into the Symfony book but are  available online for reference as part of the Symfony Cookbook. Although not officially released, you can browse the Symfony cookbook within trac at:

There are sections about emailing, cookies, paginating results, the command line interface, shopping cart plugin, and more.

Posted in  Uncategorized   |     |  No Comments »   |  delicious  Digg

May 15, 2007 – 5:54pm Extending sfPager to paginate a large array

Inspired by a snippet on the symfony site, here is a version I created that inherits from sfPager. I had a collection of objects (not propel objects) that I needed to iterate through and this did the trick. Note that this is not really optimal because the full array has to be populated during each request, whereas something like sfPropelPager is smart enough to only fetch/hydrate the objects you are going to need for that particular view. Nonetheless, this does the job pretty well.

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

May 12, 2007 – 5:29pm Left Joins with multiple conditions using Propel Criteria

Symfony by default uses Propel for its ORM. Although Propel supports supports custom SQL I have learned how to take advantage of the built-in Criteria class to help construct queries. As described in the Propel trac,

…its database neutrality and logical simplicity make it a good choice for expressing many common queries; however, for a very complex query, it may prove more effective (and less painful) to simply use a custom SQL query to hydrate your Propel objects. (propel trac)

In building a complex search system for a client, I benefited greatly from being able to add and remove columns and conditions to a criteria object on the fly without having to worry about the actual SQL syntax. However, eventually I hit a wall when I needed to add a left join using multiple columns. Adding left joins to a criteria object is typically done like this:

$c = new Criteria();
$c->addJoin(TABLE_A::ID, TABLE_B::ID, Criteria::LEFT_JOIN);
/* equivalent to: "...left join TABLE_B ON (TABLE_A.ID=TABLE_B.ID)" */

However, this syntax does not support a join with multiple conditions, so you can’t produce something like this:

...left join TABLE_B ON (TABLE_A.ID=TABLE_B.ID AND TABLE_B.VALUE &gt; 100)

In playing around quite a bit and examining the source code, I realized you could hack the addJoin method call and trick it into adding the extra column for you. The addJoin method actually is creating a new Join object that has a left and right column definition as defined in the method call. These are used to build the join condition:

$condition = $join->getLeftColumn() . '=' . $join->getRightColumn();

(see the source)

So, if you add the extra columns to the RightColumn like so:

$c->addJoin(TABLE_A::ID, TABLE_B::ID.' AND '.TABLE_B::VALUE.' > 0', Criteria::LEFT_JOIN);

the generated SQL will yield something like this:

...LEFT JOIN TABLE_B ON (TABLE_A.ID = TABLE_B.ID AND TABLE_B.VALUE > 0)

Although it’s a hack and it may not work under all conditions, so far it has been a suitable workaround.

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

May 1, 2007 – 3:00pm Installing Shared Symfony Library on Remote Host with SSH

checkout symfony from svn into ~/symfony at the root of your home directory (not into public_html or httpdocs):

svn checkout http://svn.symfony-project.com/trunk ./symfony

create your new project directory

mkdir ~/mymyNewSite

create the database on the new host
Set up subdomain for new project in cPanel
set up the symfony environment settings in the following two files:
databases.yml, properties.ini
upload the symfony project into the new project directory

./symfony sync staging go

remove the default subdomain directory from public_html and create a new symbolic link to the symfony project directory

cd ~/public_html
mv
myNewSite myNewSite.bkp
ln -s /home/stereodv/
myNewSite/web myNewSite

populate the database with the data. look in data/sql/ for lib.model.schema.sql or generated-schema.sql

mysql -u myNewSite_dbuser -p myNewSite_dbname &lt; data/sql/lib.model.schema.sql

remember to add any migrations found in data/migrate

Notes:
make sure you copy the contents of config/config.php to the server; often this file is excluded from rsync

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

April 24, 2007 – 10:02am MySQL data dump

Although I love the batch process for loading data into the database within a symfony project, sometimes it can be a bit slow if the dataset is large. I find myself reinitializing the database often while I test, not because I change the schema or add new data, but because I want to reset the data back to its initial state. For this, I run the batch process once loading the data from the yaml fixtures, and then do an SQL dump that will run much, much faster from here on out.

mysqldump -e --add-drop-table -u root -p db_name > ./data/sql/latestdump.sql

To repopulate the database with this dump file:

mysql -u root -p dbname < data/sql/latestdump.sql
Posted in  Web Development   |     |  No Comments »   |  delicious  Digg