Symfony 1.4, Doctrine and the csDoctrineActAsGeolocateablePlugin Example

I spent many hours trying to decipher the documentation for Doctrine’s Geographical Behavior and the csDoctrineActAsGeolocatablePlugin for Symfony 1.4.7.  So let me hopefully save you some time, and explain what I ran into and how I solved the problem.

I have geo-tagged content in my “yellow pages” project stored in a “Content” table, and needed to allow users to search for records within a distance of a number of various identifiers (Zip Code, City, State, GPS Coords from Mobile Devices, etc).  With this knowledge, I enabled the Geographical behavior from Doctrine to add Latitude and Longitude fields to my database.  I also imported a Census Zip Code Table and called it “GeoData”.  The GeoData table contains ZipCode, City, State, Latitude and Longitude fields; I used Geographical on this table as well.  My “Content” table also has the Geolocatable behavior attached to it, from the csDoctrineActAsGelocatablePlugin from Centre{source}.

I quickly became stumped on how to proceed from here.  I created my custom search form, built a module and created some actions and views and then … nothing.  I sat there reading documentation and experimenting by means of trial and error repeatedly until I almost put a blunt object through one of my displays.

The Doctrine documentation for Geographical has the following example:

// test.php
// ...
$q = $zipcode1->getDistanceQuery();
$q->orderby('miles asc')
  ->addWhere($q->getRootAlias() . '.city != ?', $zipcode1->city)
  ->limit(50);
echo $q->getSqlQuery();

The Geolocatable plugin has absolutely no documentation, but mentions that the following method is added to the “Table”:

Table Methods

addDistanceQuery( $query, $latitude, $longitude, $distance = null) adds distance query to a preexisting query.

field “distance” on each object represents the distance away from the passed latitude and longitude.

If $distance is not null, results are limited to that distance from the given geocodes.

After many hours, I discovered that the following code was what I needed:

$zipcode = Doctrine_Core::getTable('geoData')
  ->findOneByZipCode($this->form->getValue('zip_code'));
$query = Doctrine_Query::create()
  ->select('d.*, c.*')
  ->from('ContentDirectory d')
  ->innerJoin('d.Content c')
  ->orderBy('miles asc');
$query = Doctrine_Core::getTable('ContentDirectory')
  ->addDistanceQuery($query, $zipcode->getLatitude(), $zipcode->getLongitude(), 25);
$this->searchResults = $query->execute();

So let’s break this code down:

$zipcode = Doctrine_Core::getTable('geoData')
  ->findOneByZipCode($this->form->getValue('zip_code'));

This simply loads up an instance of my “GeoData” object for the zip code entered by the user in the form.  Since the GeoData table contains many records for the same Zip Code I call the “findOneBy” magic method.

$query = Doctrine_Query::create()
  ->select('d.*, c.*')
  ->from('ContentDirectory d')
  ->innerJoin('d.Content c')
  ->orderBy('miles asc');

This code is my base query joining my “ContentDirectory” and “Content” tables together and then ordering them by the “miles” (distance) they are from the source (zipcode from search).  I’d like to note that “miles” is not part of my data schema, and is added by the Geolocatable Plugins code in the following lines.  This Doctrine_Query should be fairly familiar to most users, so I won’t go into any detail about it.

$query = Doctrine_Core::getTable('ContentDirectory')
  ->addDistanceQuery($query, $zipcode->getLatitude(), $zipcode->getLongitude(), 25);

This bit here is where the “magic” occurs.  Since “addDistanceQuery” is a table method, I can’t just simply append it to my Doctrine_Query above.  As such, I access my Geolocatable Table (ContentDirectory) and then call the ‘addDistanceQuery’ method of that table, passing in the necessary parameters as defined by the documentation.  $query is the query we are adding the logic too, $latitude and $longitude are self explanitory and $distance allows me to limit my results within a specific distance (in miles) from the source.  If distance is not passed, then the query simply appends the “miles” logic which allows me to sort my results by distance but not limit them by it.

This may seem quite simple to some, and it does to me now as well.  However, it took quite a bit of time to realize how to string this all together.

This concludes, what I believe may be the first of many Symfony and Doctrine related posts.

1 comment for “Symfony 1.4, Doctrine and the csDoctrineActAsGeolocateablePlugin Example

  1. March 28, 2011 at 4:01 pm

    I agree the Symfony api is not well documented and while this example does not really fit my needs, it does help and more pieces to the Symfony puzzle

Leave a Reply