<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Dave Dash</title>
 <link href="http://davedash.com/tag/symfony/atom.xml" rel="self"/>
 <link href="http://davedash.com/tag/symfony"/>
 <updated>2012-04-07T22:42:44-07:00</updated>
 <id>http://davedash.com/</id>
 <author>
   <name>Dave Dash</name>
   <email>dd+atom1@davedash.com</email>
 </author>

 
 <entry>
   <title>Versioning Django Models</title>
   <link href="http://davedash.com/2009/02/13/versioning-django-models/"/>
   <updated>2009-02-13T00:00:00-08:00</updated>
   <id>http://davedash.com/2009/02/13/versioning-django-models</id>
   <content type="html">&lt;p&gt;In symfony, versioning a model was not terribly difficult.  I had my own specialized brute-force way of doing this.&lt;/p&gt;

&lt;p&gt;In my experience it's been a lot easier to write python code than PHP code, so naturally I figured this would be an easy task.  It was not.  I suspect that it was not easy because I have a naive understanding of Django and my symfony knowledge was fairly well grounded.&lt;/p&gt;

&lt;p&gt;I tried looking for a generic implementation of django model versioning, but failed.  So I came up with a specific solution.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;How I think of versions&lt;/h3&gt;

&lt;p&gt;For my app I chose to use a sparse versioning system.  I'd have one interface for interacting with a model.  For example, I have a &lt;code&gt;Restaurant&lt;/code&gt; model  and a &lt;code&gt;RestaurantVersion&lt;/code&gt; model.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Restaurant&lt;/code&gt; would directly store immutable elements like &lt;code&gt;name&lt;/code&gt; or &lt;code&gt;rating&lt;/code&gt; or &lt;code&gt;approved&lt;/code&gt; and it would encapsulate versioned elements like &lt;code&gt;description&lt;/code&gt; by proxying to &lt;code&gt;RestaurantVersion&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This means I only need to interface with one class and let the versioning happen behind the scenes.&lt;/p&gt;

&lt;h3&gt;The Django implementation of &lt;code&gt;RestaurantVersion&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;RestaurantVersion&lt;/code&gt; held the data that I thought might suffer from corruption, and therefore require reversion.  Therefore there's nothing surprising in this table:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
class RestaurantVersion(models.Model):
    restaurant       = models.ForeignKey('Restaurant', null=True, blank=True)
    user             = models.ForeignKey(Profile, null=True, blank=True)
    description      = models.TextField(blank=True)
    html_description = models.TextField(blank=True)
    url              = models.CharField(max_length=765, blank=True)
    created_at       = models.DateTimeField(auto_now_add=True)
    
    def save(self, force_insert=False, force_update=False):
        self.html_description = markdown(self.description)
        super(RestaurantVersion, self).save(force_insert, force_update)
        
    class Meta:
        db_table = u'restaurant_version'

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The only thing of note is that I'm enforcing a &lt;code&gt;1:M&lt;/code&gt; relation between &lt;code&gt;Restaurant&lt;/code&gt; and &lt;code&gt;RestaurantVersion&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;The Django implementation of &lt;code&gt;Restaurant&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Restaurant&lt;/code&gt; is where the magic happens.  It needs to do a few things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do what a normal object does.&lt;/li&gt;
&lt;li&gt;Proxy attributes to the corresponding attribute of a &lt;code&gt;RestaurantVersion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Generate a new &lt;code&gt;RestaurantVersion&lt;/code&gt; on demand.&lt;/li&gt;
&lt;li&gt;(optional) Manage which version is &quot;active&quot;.&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;I say &quot;4&quot; is optional because I don't do this myself.  I built versioning into a few models because it was easier to do upfront than worry about it down the road.&lt;/p&gt;

&lt;p&gt;Here's the code I use:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;

class Restaurant(models.Model):
    name           = models.CharField(max_length=765, blank=True)
    stripped_title = models.CharField(max_length=384, blank=True)
    approved       = models.IntegerField(null=True, blank=True)
    version        = models.ForeignKey(RestaurantVersion, related_name=&quot;the_restaurant&quot;)
    updated_at     = models.DateTimeField(auto_now=True)
    created_at     = models.DateTimeField(auto_now_add=True)
    new_version    = None
    
    def save(self, force_insert=False, force_update=False):
        if not self.stripped_title:
            self.stripped_title = slugify(self.name)
        
        super(Restaurant, self).save(force_insert, force_update)
        if self.new_version:
            self.new_version.restaurant = self
            self.new_version.save()
            self.version = self.new_version
            super(Restaurant, self).save(force_insert, force_update)        
        
    def __setattr__(self, name, value):
        if name == 'description':
            self.get_new_version().description = value
        
        elif name == 'url':
            self.get_new_version().url = value
        
        else:
            object.__setattr__(self, name, value)

    def __getattr__(self, name):
        try:
            if name == 'description':
                return self.version.description

            elif name == 'url':
                return self.version.url
        except RestaurantVersion.DoesNotExist:
            return ''
            
        models.Model.__getattribute__(self, name)
    
    def get_new_version(self):
        if self.new_version == None:
            try:
                rv    = self.version
                rv.id = None
            except RestaurantVersion.DoesNotExist:
                rv = RestaurantVersion()
            
            self.new_version = rv

        return self.new_version
            
    class Meta:
        db_table     = u'restaurant'
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;It's not rocket science, but it wasn't necessarily easy either.  Let's look at requirements 2 and 4 in a bit more detail.&lt;/p&gt;

&lt;h4&gt;Proxy attributes to the corresponding &lt;code&gt;RestaurantVersion&lt;/code&gt; attributes&lt;/h4&gt;

&lt;p&gt;Proxying attributes is a way of masking the whole versioning infrastructure from the developer.  We do this with &lt;code&gt;__getattr__&lt;/code&gt; and &lt;code&gt;__setattr__&lt;/code&gt; methods.&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
    def __getattr__(self, name):
        try:
            if name == 'description':
                return self.version.description

            elif name == 'url':
                return self.version.url
        except RestaurantVersion.DoesNotExist:
            return ''
            
        models.Model.__getattribute__(self, name)
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;__getattr__&lt;/code&gt; determins if you are looking for &lt;code&gt;description&lt;/code&gt; or &lt;code&gt;url&lt;/code&gt; attributes of a &lt;code&gt;Restaurant&lt;/code&gt;.  If you are then it uses the current &lt;code&gt;RestaurantVersion&lt;/code&gt;'s attribute.&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
    def __setattr__(self, name, value):
        if name == 'description':
            self.get_new_version().description = value
        
        elif name == 'url':
            self.get_new_version().url = value
        
        else:
            object.__setattr__(self, name, value)
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;__setattr__&lt;/code&gt; is very similar, except since we're changing a versionable attribute we're going to make a request to a method, &lt;code&gt;get_new_version()&lt;/code&gt;, which we will detail later.  It does what it says, though, it gets the &quot;new&quot; &lt;code&gt;RestaurantVersion&lt;/code&gt; of the &lt;code&gt;Restaurant&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;Generate a new &lt;code&gt;RestaurantVersion&lt;/code&gt; on demand&lt;/h4&gt;

&lt;p&gt;As you can see from the above code, I am &quot;auto-versioning&quot;.  This is done mostly via &lt;code&gt;get_new_version()&lt;/code&gt;.  We also have to do a few tricks to make sure the bidirectional relationship gets maintained on save.&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
    def get_new_version(self):
        if self.new_version == None:
            try:
                rv    = self.version
                rv.id = None
            except RestaurantVersion.DoesNotExist:
                rv = RestaurantVersion()
            
            self.new_version = rv

        return self.new_version
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;get_new_version()&lt;/code&gt; either returns the current &quot;new version&quot;, a brand new &quot;new version&quot; or a &quot;copy&quot; of the current version.&lt;/p&gt;

&lt;p&gt;The &quot;copy&quot; is done simply by setting the &lt;code&gt;id&lt;/code&gt; attribute of the new version to &lt;code&gt;None&lt;/code&gt;.  Django takes care of assigning it a new &lt;code&gt;id&lt;/code&gt; on save, thus preserving the old version.&lt;/p&gt;

&lt;p&gt;Note that when we create a brand new &lt;code&gt;RestaurantVersion&lt;/code&gt;, we don't immediately set it's &lt;code&gt;restaurant&lt;/code&gt; attribute.  That's because we haven't saved the current &lt;code&gt;restaurant&lt;/code&gt; yet.  It's a &quot;chicken and the egg&quot; problem that gets solved in our &lt;code&gt;save()&lt;/code&gt; method:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;python&quot;&gt;
    def save(self, force_insert=False, force_update=False):
        if not self.stripped_title:
            self.stripped_title = slugify(self.name)
        
        super(Restaurant, self).save(force_insert, force_update)
        if self.new_version:
            self.new_version.restaurant = self
            self.new_version.save()
            self.version = self.new_version
            super(Restaurant, self).save(force_insert, force_update)
        
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;We first save the &lt;code&gt;Restaurant&lt;/code&gt;, then we save a new version if there is one.  If we save a new version we want to update the &lt;code&gt;Restaurant&lt;/code&gt; a second time.  This ensures that there's a &lt;code&gt;1:1&lt;/code&gt; relationship between &lt;code&gt;Restaurant&lt;/code&gt; and the current &lt;code&gt;RestaurantVersion&lt;/code&gt; and it also establishes a &lt;code&gt;1:M&lt;/code&gt; relationship between &lt;code&gt;Restaurant&lt;/code&gt; and &lt;em&gt;all&lt;/em&gt; &lt;code&gt;RestaurantVersion&lt;/code&gt;s.&lt;/p&gt;

&lt;h3&gt;Conclusion and Drawbacks&lt;/h3&gt;

&lt;p&gt;While, I think that this setup works in my particular situation, I feel like it has some major flaws.&lt;/p&gt;

&lt;p&gt;The code is quite messy and very specific to this model.  My goal was to take care of this quickly, not necessarily in a reusable manner.  I also was not familiar enough with the Django model to create some sort of extension.&lt;/p&gt;

&lt;p&gt;This way of versioning does not allow for versioned attributes to be set upon instantiation of the model:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;r=Restaurant(description=&quot;Great place&quot;) 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;won't work.  You'll have to do:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;r=Restaurant()
r.description=&quot;Great place&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I figured this was acceptable.&lt;/p&gt;

&lt;p&gt;Lastly I'm not entirely happy with having to explicitly save the &lt;code&gt;Restaurant&lt;/code&gt; model twice, but I think my bidirectional relation requires this.&lt;/p&gt;

&lt;p&gt;All in all this works in my particular situation.  I'm hoping that this can be simplified.  I'm curious to hear about other versioning schemes.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Mimic propel's update_at and created_at in Django models</title>
   <link href="http://davedash.com/2008/11/29/mimic-propels-update_at-and-created_at-in-django-models/"/>
   <updated>2008-11-29T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/11/29/mimic-propels-update_at-and-created_at-in-django-models</id>
   <content type="html">&lt;p&gt;One &quot;trick&quot; that propel offers you is tables with fields &lt;code&gt;created_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fields are self explanatory and within Propel you just define them and they just do their business.&lt;/p&gt;

&lt;p&gt;To simulate this in Django just do:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;auto_now&lt;/code&gt; automatically sets a field to the current time, and &lt;code&gt;auto_now_add&lt;/code&gt; only does this if the object is being added (not updated).&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>sfGuardUser -> django.contrib.auth</title>
   <link href="http://davedash.com/2008/11/20/sfguarduser-djangocontribauth/"/>
   <updated>2008-11-20T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/11/20/sfguarduser-djangocontribauth</id>
   <content type="html">&lt;p&gt;If you find yourself moving from symfony to Django, here's how you &lt;code&gt;sf_guard_user&lt;/code&gt;'s user table to &lt;code&gt;django.contrib.auth&lt;/code&gt; user table:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;INSERT INTO 
    auth_user (id, username, password, is_active, last_login, date_joined)
SELECT 
    id, 
    username, 
    CONCAT(algorithm,'$', salt, '$', password), 
    1, 
    last_login, 
    created_at 
FROM sf_guard_user;

DROP TABLE sf_guard_user;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Luckily, django uses a similar salting and encryption strategy as symfony, so it's easy to go back and forth.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Why code rewrites are often coupled with redesigns</title>
   <link href="http://davedash.com/2008/08/13/why-code-rewrites-are-often-coupled-with-redesigns/"/>
   <updated>2008-08-13T00:00:00-07:00</updated>
   <id>http://davedash.com/2008/08/13/why-code-rewrites-are-often-coupled-with-redesigns</id>
   <content type="html">&lt;p&gt;I've done some rewrites of code, and they usually are coupled with redesigns.&lt;/p&gt;

&lt;p&gt;Redesigns and rewrites are tricky.  With web sites existing users tend to prefer incremental changes with each.  Changing a design element or a feature are pretty much equivalent when its done incrementally.&lt;/p&gt;

&lt;p&gt;Usually small changes are like surgery.  You make a small change, you possibly announce it, and people look at it and usually say yes, this change is better.  But mostly a small change can go unnoticed.&lt;/p&gt;

&lt;p&gt;Tiny atomic changes are nice and they improve the product.&lt;/p&gt;

&lt;p&gt;A rewrite of code however is a discrete change.  They take forever.  Second system and all that.  They inevitably involve a redesign as well.  They tend to be received with mixed reviews.&lt;/p&gt;

&lt;p&gt;Generally a design of a site is grafted onto code.  Sometimes by templating languages, sometimes in a tightly coupled system.  In either case there's usually two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make a faithful port of the design.&lt;/li&gt;
&lt;li&gt;Redesign&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;The latter is generally preferred since this saves a lot of time.  Generally there are bugs with a design, and there is a design backlog.   A faithful port of the design would mean porting every broken HTML code, every non user-friendly element, etc.&lt;/p&gt;

&lt;p&gt;Unfortunately this leaves web developers with a scary situation: Launching a product which changes peoples functional and UI experience.&lt;/p&gt;

&lt;p&gt;The good thing is redesigns usually mean evolutionary leaps.  Meaning, iterations can happen faster, both in design and functionality.  We can easily please new customers, and we can quickly bring current customers up to speed.&lt;/p&gt;

&lt;p&gt;In my spare time, I've been rewriting code that I wrote in symfony (PHP) into django (python).  It's a tiresome long process that can easily kill a side project.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>... and the new Delicious has landed</title>
   <link href="http://davedash.com/2008/07/31/and-the-new-delicious-has-landed/"/>
   <updated>2008-07-31T00:00:00-07:00</updated>
   <id>http://davedash.com/2008/07/31/and-the-new-delicious-has-landed</id>
   <content type="html">&lt;p&gt;Working at Delicious with the team has been a wonderful experience.  This is probably the most fun I've had working on a project.  I am really glad to work on a site that I use almost daily (and regularly if not daily before I joined) with a &lt;a href=&quot;http://delicious.com/about#team&quot;&gt;team that really cares about their product&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So without further ado...&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://delicious.com/&quot;&gt;Delicious 2.0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's &lt;a href=&quot;http://blog.delicious.com/blog/2008/07/oh-happy-day.html&quot;&gt;the official announcement&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;object type=&quot;application/x-shockwave-flash&quot; width=&quot;400&quot; height=&quot;300&quot; data=&quot;http://www.flickr.com/apps/video/stewart.swf?v=55430&quot; classid=&quot;clsid:D27CDB6E-AE6D-11cf-96B8-444553540000&quot;&gt; &lt;param name=&quot;flashvars&quot; value=&quot;intl_lang=en-us&amp;amp;photo_secret=3f35e658c4&amp;amp;photo_id=2718285703&quot;&gt;&lt;/param&gt; &lt;param name=&quot;movie&quot; value=&quot;http://www.flickr.com/apps/video/stewart.swf?v=55430&quot;&gt;&lt;/param&gt; &lt;param name=&quot;bgcolor&quot; value=&quot;#000000&quot;&gt;&lt;/param&gt; &lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;/param&gt;&lt;embed type=&quot;application/x-shockwave-flash&quot; src=&quot;http://www.flickr.com/apps/video/stewart.swf?v=55430&quot; bgcolor=&quot;#000000&quot; allowfullscreen=&quot;true&quot; flashvars=&quot;intl_lang=en-us&amp;amp;photo_secret=3f35e658c4&amp;amp;photo_id=2718285703&quot; height=&quot;300&quot; width=&quot;400&quot;&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/p&gt;

&lt;p&gt;And here's &lt;a href=&quot;http://www.flickr.com/photos/davedash/sets/72157606475284163&quot;&gt;some photos I took&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>... and the new Delicious has landed</title>
   <link href="http://davedash.com/2008/07/31/and-the-new-delicious-has-landed/"/>
   <updated>2008-07-31T00:00:00-07:00</updated>
   <id>http://davedash.com/2008/07/31/and-the-new-delicious-has-landed</id>
   <content type="html">&lt;p&gt;Working at Delicious with the team has been a wonderful experience.  This is probably the most fun I've had working on a project.  I am really glad to work on a site that I use almost daily (and regularly if not daily before I joined) with a &lt;a href=&quot;http://delicious.com/about#team&quot;&gt;team that really cares about their product&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So without further ado...&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://delicious.com/&quot;&gt;Delicious 2.0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's &lt;a href=&quot;http://blog.delicious.com/blog/2008/07/oh-happy-day.html&quot;&gt;the official announcement&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;object type=&quot;application/x-shockwave-flash&quot; width=&quot;400&quot; height=&quot;300&quot; data=&quot;http://www.flickr.com/apps/video/stewart.swf?v=55430&quot; classid=&quot;clsid:D27CDB6E-AE6D-11cf-96B8-444553540000&quot;&gt; &lt;param name=&quot;flashvars&quot; value=&quot;intl_lang=en-us&amp;amp;photo_secret=3f35e658c4&amp;amp;photo_id=2718285703&quot;&gt;&lt;/param&gt; &lt;param name=&quot;movie&quot; value=&quot;http://www.flickr.com/apps/video/stewart.swf?v=55430&quot;&gt;&lt;/param&gt; &lt;param name=&quot;bgcolor&quot; value=&quot;#000000&quot;&gt;&lt;/param&gt; &lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;/param&gt;&lt;embed type=&quot;application/x-shockwave-flash&quot; src=&quot;http://www.flickr.com/apps/video/stewart.swf?v=55430&quot; bgcolor=&quot;#000000&quot; allowfullscreen=&quot;true&quot; flashvars=&quot;intl_lang=en-us&amp;amp;photo_secret=3f35e658c4&amp;amp;photo_id=2718285703&quot; height=&quot;300&quot; width=&quot;400&quot;&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/p&gt;

&lt;p&gt;And here's &lt;a href=&quot;http://www.flickr.com/photos/davedash/sets/72157606475284163&quot;&gt;some photos I took&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>nginx proxying to apache</title>
   <link href="http://davedash.com/2008/04/16/nginx-proxying-to-apache/"/>
   <updated>2008-04-16T00:00:00-07:00</updated>
   <id>http://davedash.com/2008/04/16/nginx-proxying-to-apache</id>
   <content type="html">&lt;p&gt;I gave up on fastcgi with NginX and django.  Too many things just didn't work, so I decided to keep Apache, but lock it down and thrown NginX in the front to serve static content and to prevent max client issues.&lt;/p&gt;

&lt;p&gt;I also applied a similar approach for symfony.  Server configurations after the jump...&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;Apache&lt;/h3&gt;

&lt;p&gt;You should configure Apache as you would if you were serving directly from Apache.  In otherwords, if you are already using Apache, you don't need to do anything different, save changing a port.&lt;/p&gt;

&lt;p&gt;The one tweak I made to a standard configuration is to limit the allowed hosts to just the ones I want.&lt;/p&gt;

&lt;p&gt;Here's my django app's &lt;code&gt;apache.conf&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;NameVirtualHost *
&amp;lt;VirtualHost *&amp;gt;
    ServerName onyxfoundation.org

    SetEnvIf X-Magic-Header ^secret$ let_me_in
    &amp;lt;Location &quot;/&quot;&amp;gt;
            SetHandler python-program
            PythonHandler django.core.handlers.modpython
            SetEnv DJANGO_SETTINGS_MODULE onyx.settings
            PythonDebug On
            PythonPath &quot;['/var/www/django/'] + sys.path&quot;
            Order Deny,Allow
            Deny from all
            Allow from env=let_me_in
    &amp;lt;/Location&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note the &lt;code&gt;SetEnvIf&lt;/code&gt; it can be set however you want, I prefer to match a specific header that I send from &lt;code&gt;nginx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you are using symfony, something like this will be more appropriate:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;VirtualHost *&amp;gt;
        ServerName reviewsby.us
        DocumentRoot /var/www/reviewsby.us/web
        DirectoryIndex index.php
       SetEnvIf X-Magic-Header ^secret$ let_me_in

        &amp;lt;Directory /var/www/reviewsby.us/web&amp;gt;
                Options Indexes FollowSymLinks

                RewriteEngine On

                RewriteRule ^$ index.html [QSA]
                RewriteRule ^([^.]+)$ $1.html [QSA]

                RewriteCond %{REQUEST_FILENAME} !-f
                RewriteRule ^(.*)$ index.php [QSA]
                Order Deny,Allow
                Deny from all
                Allow from env=let_me_in
        &amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You'll also need to listen on a different port (preferably one that is not exposed to the outside world):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Listen 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;NginX&lt;/h3&gt;

&lt;p&gt;Now NginX needs to be configured to do 2 things:
1. Serve all static content
2. Pass requests that need interpreting to django or symfony&lt;/p&gt;

&lt;p&gt;The Django conf of Nginx is very easy:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;server {
  listen   80;
  server_name onyxfoundation.org;

  location ^~ /static/ {
    alias        /var/www/django/onyx/static/;
    access_log   off;
    expires      30d;
  }

  location / {
    proxy_pass      http://127.0.0.1:8080/;
    proxy_redirect  off;

    proxy_set_header   Host             $host;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

    proxy_set_header        X-Magic-Header &quot;secret&quot;;
    client_max_body_size       10m;
    client_body_buffer_size    128k;

    proxy_connect_timeout      90;
    proxy_send_timeout         90;
    proxy_read_timeout         90;

    proxy_buffer_size          4k;
    proxy_buffers              4 32k;
    proxy_busy_buffers_size    64k;
    proxy_temp_file_write_size 64k;

  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The settings are trivial.  Everything that's &lt;code&gt;/static&lt;/code&gt; gets served straight from NginX and the django application is served over Apache.&lt;/p&gt;

&lt;p&gt;The same thing is a bit trickier in symfony:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;server {
  listen   80;
  server_name  reviewsby.us;

  charset utf-8;

  location  /sf/ {
    alias  /var/www/reviewsby.us/lib/vendor/symfony/data/web/sf/;
  }
  location /css/ {
    alias /var/www/reviewsby.us/web/css/;
  }
  location /images/ {
    alias /var/www/reviewsby.us/web/images/;
  }

  location /js/ {
    alias /var/www/reviewsby.us/web/js/;
  }

  location / { 
    proxy_pass http://reviewsby.us:8080/;
    proxy_redirect off;

    proxy_set_header   Host             $host;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

    proxy_set_header        X-Magic-Header &quot;secret&quot;;
    client_max_body_size       10m;
    client_body_buffer_size    128k;

    proxy_connect_timeout      90;
    proxy_send_timeout         90;
    proxy_read_timeout         90;

    proxy_buffer_size          4k;
    proxy_buffers              4 32k;
    proxy_busy_buffers_size    64k;
    proxy_temp_file_write_size 64k;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The added difficulty with symfony is specific to this app (and most default apps) since static content is split amongst the web root (&lt;code&gt;/js&lt;/code&gt;, &lt;code&gt;/css&lt;/code&gt;, &lt;code&gt;/images&lt;/code&gt;, etc) versus the suggested django approach of throwing everything in &lt;code&gt;/static&lt;/code&gt;.  I now adopt this approach for symfony as well.  It makes everything more flexible in the long-run.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;If you've been hesitating about putting a reverse proxy in front of Apache, hopefully this can help you out.  NginX is a lightweight fast server that can handle far more requests than Apache.  By putting it on the front-lines you can give your app that extra inch it needs to keep churning out requests.&lt;/p&gt;

&lt;p&gt;Let me know if this configuration works for you.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Optimization heuristics</title>
   <link href="http://davedash.com/2008/02/26/optimization-heuristics/"/>
   <updated>2008-02-26T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/26/optimization-heuristics</id>
   <content type="html">&lt;p&gt;I decided to play version control private-eye today when my coworker mentioned that we make a system call to check the time several times per request on a few specific pages.&lt;/p&gt;

&lt;p&gt;My analysis was we didn't need to have the calls but it had me thinking...&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;The micro-caching heuristic&lt;/h3&gt;

&lt;p&gt;One way of optimizing code is by calculating expensive operations once and storing them in a specific location.  It's a micro-cache if you will of data.&lt;/p&gt;

&lt;p&gt;One obvious example ends up being loops:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
// inefficient
for ($i = 0; $i &lt; count($arr); $i++)
{
// magic
}

// efficient
$arr_size = count($arr);
for ($i = 0; $i &lt; $arr_size; $i++)
{
// magic
}
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The inefficient example computes &lt;code&gt;count($arr)&lt;/code&gt; &lt;em&gt;n&lt;/em&gt; times where &lt;em&gt;n&lt;/em&gt; is the size of the array &lt;code&gt;$arr&lt;/code&gt;.  The latter computes it once.&lt;/p&gt;

&lt;p&gt;When we write and review code we should look out for loops and repeated code.  These elements can be pulled out.&lt;/p&gt;

&lt;p&gt;With some data lookups like system calls or queries we can apply a request-wide caching.  In a symfony app, for example, we might need to calculate the time stamp in several locations throughout the code.  Rather than calling &lt;code&gt;time()&lt;/code&gt; throughout the code, we can create a site wide accessible attribute that has the same value.&lt;/p&gt;

&lt;p&gt;For example, we can subclass &lt;code&gt;sfContext&lt;/code&gt; and add a method called &lt;code&gt;getTime()&lt;/code&gt; which runs &lt;code&gt;time()&lt;/code&gt; once and only once per request and stores it internally.  Whenever part of the app needs the time stamp, &lt;code&gt;myContext::getInstance()-&amp;gt;getTime()&lt;/code&gt; (or an equivalent shortcut) can be called.  Of course, I haven't tested this, and &lt;code&gt;myContext&lt;/code&gt; might not be the best place for this, but a similar strategy will work.&lt;/p&gt;

&lt;p&gt;-dd&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Redo The Web</title>
   <link href="http://davedash.com/2008/02/12/redo-the-web/"/>
   <updated>2008-02-12T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/12/redo-the-web</id>
   <content type="html">&lt;p&gt;Sometimes it's nice to read a blog the &quot;normal&quot; way.  You go to the site and just read the entries in reverse chronological order ... versus the RSS-crazy aggregated with everything else.&lt;/p&gt;

&lt;p&gt;François Zaninotto's &lt;a href=&quot;http://redotheweb.com/&quot;&gt;Redo The Web&lt;/a&gt; is a great blog for that if you do any work with &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; or web development in general.  François wrote most of the documentation for symfony including &lt;em&gt;The Definitive Guide to Symfony&lt;/em&gt; and his blog manages to keep that same easy-to-follow tips and tutorials.&lt;/p&gt;

&lt;p&gt;Definitely check it out.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Keep a second web server around for luck...</title>
   <link href="http://davedash.com/2008/02/12/keep-a-second-web-server-around-for-luck/"/>
   <updated>2008-02-12T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/12/keep-a-second-web-server-around-for-luck</id>
   <content type="html">&lt;p&gt;I had one of those mid-day &quot;what's going on with my server&quot; heart-atacks.  I have a service that emails me when &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; is down.  On my old server if it went down, I could just restart the server and it'd be back up.  That was big old apache, running out of memory or something.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://reviewsby.us/&quot;&gt;Reviewsby.us&lt;/a&gt; is a medium sized site.  It gets a fair amount of traffic at a steady pace.  Even in this case I decided I was in need for a new server, so I looked into nginx.  It's fast and it can serve static content well and pass things to fastcgi.  Joshua Schachter explains the &lt;a href=&quot;http://joshua.schachter.org/2008/01/proxy.html&quot;&gt;proxy in front&lt;/a&gt; concept pretty well.&lt;/p&gt;

&lt;p&gt;Back to my web developer heart attack...&lt;/p&gt;

&lt;p&gt;Well this setup had been holding up for a better part of a month fairly well... then I saw that a lot of the pages just lagged.  I restarted fastcgi and nginx (it was a fastcgi issue).  Rather than try to debug something I couldn't, I quickly installed apache2 and setup the server the tried and tested way.&lt;/p&gt;

&lt;p&gt;This all took place in a half an hour.  Not the end of the world, but not elegant either.  In the future, I'll revert to using nginx (possibly nginx+apache versus nginx+fastcgi) but I'll keep my other configurations around when all hell breaks loose.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>the magic of django: get_callable</title>
   <link href="http://davedash.com/2008/02/08/the-magic-of-django-get_callable/"/>
   <updated>2008-02-08T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/08/the-magic-of-django-get_callable</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; is an amazing framework simply because of the uphill battle it has
with PHP.  There's a lot of behind the scenes magic that ameliorates the
frustrations one might have with PHPs lack of syntactical magic.&lt;/p&gt;

&lt;p&gt;A lot of work is done to make sure certain things are autoloaded and that
certain things behave the way you expect.&lt;/p&gt;

&lt;p&gt;I hadn't had a chance to peek under Django's hood too much myself, but it
appears that despite the inherent magic of python, there were some much needed
additions.  My savior for today was &lt;code&gt;get_callable&lt;/code&gt;.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;I have a very particular vision of interacting with OpenID.  Basically I like
to decouple as much of the OpenID interactions away from the inner workings of
my web site.  I do, however, want a hook where I can supply some of my own
logic after OpenID has been verified.&lt;/p&gt;

&lt;p&gt;It's roughly how I implimented the sfOpenIDPlugin.   Luckily this time, I was
able to rely on the &lt;a href=&quot;http://www.openidenabled.com/openid/libraries/python/&quot;&gt;OpenID 2.1
libraries&lt;/a&gt; from
OpenIDEnabled.&lt;/p&gt;

&lt;p&gt;I did ditch their example code, because I wanted a sexy package.  The package
isn't quite ready for primetime, but you can &lt;a href=&quot;http://svn.spindrop.us/django/trunk/&quot;&gt;have a
gander&lt;/a&gt; (and yes, I do welcome
collaborators and am open to merging my work with the myriad of work that's out
there).&lt;/p&gt;

&lt;h3&gt;Getting to the point&lt;/h3&gt;

&lt;p&gt;Anyway, &lt;code&gt;get_callable&lt;/code&gt; is what allowed me to write my &quot;hook&quot; into my OpenID
logic.&lt;/p&gt;

&lt;p&gt;In my &lt;code&gt;settings.py&lt;/code&gt; I defined:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;OPENID_SUCCESS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;myproject.myapp.views.openid_handler&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;The way my OpenID library works, is it does it's business and when it verifies
a url belongs to someone it then delivers he request to the &lt;code&gt;OPENID_SUCCESS&lt;/code&gt;
hook.  Here's how:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OPENID_SUCCESS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_callable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OPENID_SUCCESS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;&lt;code&gt;get_callable&lt;/code&gt; works some python magic by splitting
&lt;code&gt;myproject.myapp.views.openid_handler&lt;/code&gt; into the module: &lt;code&gt;myproject.myapp.views&lt;/code&gt;
and the attribute &lt;code&gt;openid_handler&lt;/code&gt;.  It uses this to produce a callable object.
In the example above I gave it parameters of &lt;code&gt;request&lt;/code&gt; and &lt;code&gt;result&lt;/code&gt; as they
were required by my app.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>sfGuardUser to django.contrib.auth.models.User</title>
   <link href="http://davedash.com/2008/02/07/sfguarduser-to-djangocontribauthmodelsuser/"/>
   <updated>2008-02-07T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/07/sfguarduser-to-djangocontribauthmodelsuser</id>
   <content type="html">&lt;p&gt;Let's pretend that your assignment was to convert a symfony app that used sfGuardAuth to a django-based system.  Wouldn't it be great if someone just gave you a bunch of SQL that you could use to convert the &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; &lt;code&gt;sf_guard_user&lt;/code&gt; table to a &lt;a href=&quot;http://djangoproject.com/&quot;&gt;django&lt;/a&gt; &lt;code&gt;auth_user&lt;/code&gt; table?&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;Here you go:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;REPLACE INTO 
  auth_user

SELECT 
  p.id AS id, 
  replace(username, 'http://', '') AS username,
  null AS first_name,
  null AS last_name,
  email,
  concat(algorithm,'$',salt,'$',password),
  0 as is_staff,
  1 as is_active,
  0 as is_superuser, 
  last_login,
  u.created_at as date_joined


FROM 
  `sf_guard_user` u, profile p 

WHERE
  p.userid = u.id
&lt;/code&gt;&lt;/pre&gt;
</content>
 </entry>
 
 <entry>
   <title>Why Django Templating is awesome and why I get smarty again</title>
   <link href="http://davedash.com/2008/02/01/why-django-templating-is-awesome-and-why-i-get-smarty-again/"/>
   <updated>2008-02-01T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/02/01/why-django-templating-is-awesome-and-why-i-get-smarty-again</id>
   <content type="html">&lt;p&gt;I get Smarty thanks to django... yeah, it's weird.&lt;/p&gt;

&lt;p&gt;Back to my &lt;a href=&quot;http://spindrop.us/2008/01/28/templating/&quot;&gt;original comment about templating&lt;/a&gt;, smarty really is trying to limit the scope of PHP in a good way.  Too often I see a lot of heavy-lifting in the templates.  It's so bad it makes my MVC'd brain explode.&lt;/p&gt;

&lt;p&gt;Django templates are very limited, based on a philosophy of keeping view logic and only view logic in the templates.&lt;/p&gt;

&lt;p&gt;This is what smart tries to do and it's a reasonable solution to the fact that PHP is a templating language with a kitchen sink.  It's saying, okay... well let's treat PHP as a programming language, and keep Smarty as the template.&lt;/p&gt;

&lt;p&gt;symfony of course says (well not really, or in any official capacity, but would you believe... frameworks talk to me), some PHP is view and some is model/controller.  We'll suggest which ones are which, but really we're not getting in the way of making your life a living hell by sticking complicated business logic inline.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>reusability</title>
   <link href="http://davedash.com/2008/01/30/reusability/"/>
   <updated>2008-01-30T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/30/reusability</id>
   <content type="html">&lt;p&gt;[tags]django, plugins, apps, projects, symfony[/tags]&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;A project is a collection of settings for an instance of Django, including database configuration, Django-specific options, and application-specific settings.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.djangobook.com/en/1.0/chapter02/&quot;&gt;The Django Book, Chapter 2&lt;/a&gt;:&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;A few people have been asking for more comparisons between symfony and Django.  For me it's a great way of understanding Django and python as well as symfony and PHP.&lt;/p&gt;

&lt;p&gt;Reusability is at the core of Django, not an afterthought.  The only unique part of an app is the settings and the views.  Everything else is an application that can exist independently of your app.  It's nice and decoupled.&lt;/p&gt;

&lt;p&gt;This wouldn't be impossible to do in symfony.  Each module could be designed from the start as a plugin.  Complete with its own set of models and default templates.  The configuration of a project/app could then make the web app unique.&lt;/p&gt;

&lt;p&gt;Right now the bulk of my symfony models are tightly coupled to their apps.  It's a little confusing, but there isn't a direct correlation between Django projects, Django apps and symfony Projects, apps and modules.  Each kind of overlaps one another.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Templating</title>
   <link href="http://davedash.com/2008/01/28/templating/"/>
   <updated>2008-01-28T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/28/templating</id>
   <content type="html">&lt;p&gt;[tags]smarty, symfony, php, python, django[/tags]&lt;/p&gt;

&lt;p&gt;I used to use the Smarty templating system quite heavily in my PHP apps.  Then after switching over to &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, I broke that habit.&lt;/p&gt;

&lt;p&gt;PHP &lt;em&gt;is&lt;/em&gt; a templating language for better or worse.  Smarty was a way of saying that &quot;well things could be easier&quot;, but that's against the grain of what PHP fundamentally is.&lt;/p&gt;

&lt;p&gt;One frustration of PHP is this.  A &quot;pure&quot; PHP file with no HTML looks like this:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    &lt;?php
    
    // code goes here
    // ...
    
    // end of file
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;PHP is an HTML templating system, by default everything in PHP is just stuff waiting to be sent untouched to the browser.  So in essence, PHP web apps aren't really web apps... they are very complicated templates all talking to each other.&lt;/p&gt;

&lt;p&gt;So you'll note in the above example, even a pure PHP file must begin with &lt;code&gt;&amp;lt;?php&lt;/code&gt;, and signal that we're actually doing code.  In fact, to prevent any unintended whitespace being sent to the browser, a PHP only file will by convention omit the closing &lt;code&gt;?&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So my shiny new Django framework is a breath of fresh air.  There's no &lt;code&gt;&amp;lt;?py&lt;/code&gt; tags, it's all just python.  And template files are explicitly template files parsed by a template engine built on top of a scripting language.&lt;/p&gt;

&lt;p&gt;I can easily see PHP evolving into something similar one day.  Where all PHP files are just PHP by default.  It would be more in line with the way MVC development is going.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Templating</title>
   <link href="http://davedash.com/2008/01/28/templating/"/>
   <updated>2008-01-28T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/28/templating</id>
   <content type="html">&lt;p&gt;[tags]smarty, symfony, php, python, django[/tags]&lt;/p&gt;

&lt;p&gt;I used to use the Smarty templating system quite heavily in my PHP apps.  Then after switching over to &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, I broke that habit.&lt;/p&gt;

&lt;p&gt;PHP &lt;em&gt;is&lt;/em&gt; a templating language for better or worse.  Smarty was a way of saying that &quot;well things could be easier&quot;, but that's against the grain of what PHP fundamentally is.&lt;/p&gt;

&lt;p&gt;One frustration of PHP is this.  A &quot;pure&quot; PHP file with no HTML looks like this:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    &lt;?php
    
    // code goes here
    // ...
    
    // end of file
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;PHP is an HTML templating system, by default everything in PHP is just stuff waiting to be sent untouched to the browser.  So in essence, PHP web apps aren't really web apps... they are very complicated templates all talking to each other.&lt;/p&gt;

&lt;p&gt;So you'll note in the above example, even a pure PHP file must begin with &lt;code&gt;&amp;lt;?php&lt;/code&gt;, and signal that we're actually doing code.  In fact, to prevent any unintended whitespace being sent to the browser, a PHP only file will by convention omit the closing &lt;code&gt;?&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So my shiny new Django framework is a breath of fresh air.  There's no &lt;code&gt;&amp;lt;?py&lt;/code&gt; tags, it's all just python.  And template files are explicitly template files parsed by a template engine built on top of a scripting language.&lt;/p&gt;

&lt;p&gt;I can easily see PHP evolving into something similar one day.  Where all PHP files are just PHP by default.  It would be more in line with the way MVC development is going.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>django experiment: day 2 templates, and that's it?</title>
   <link href="http://davedash.com/2008/01/28/django-experiment-day-2-templates-and-thats-it/"/>
   <updated>2008-01-28T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/28/django-experiment-day-2-templates-and-thats-it</id>
   <content type="html">&lt;p&gt;[tags]django, symfony[/tags]&lt;/p&gt;

&lt;p&gt;[&lt;strong&gt;Note&lt;/strong&gt;: This really does have something to do with symfony.]&lt;/p&gt;

&lt;p&gt;So I finished the tutorials on Django, but the Django tutorial is nothing compared to the Askeet Advent Calendar.  Askeet was 24 one hour tutorials that go in depth to each nook and cranny of &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, whereas I still feel like there's got to be more to Django.&lt;/p&gt;

&lt;p&gt;I get Templating now, and I really like the flexibility offered in Django.  The one major difference is the layout structure.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; has this nice structure of defining a general layout, and then all following templates automatically inherit from the default layout (unless otherwise specified).  Django on the other hand requires you to explicitly extend other layouts and define regions within those layouts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I like the symfony style because I inherit almost always from the general template.&lt;/li&gt;
&lt;li&gt;The Django style however offers the flexibility of having subsections (which inherit from the base style) which you can inherit from.  E.g. you may have a site which has 1. a blog and 2. a gallery.  Both the blog and gallery sections share common elements from a main template, but all the pages within blog all have some unique bloggish features that the gallery lacks.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;I've got a lot more to learn with Django, but I am enjoying it so far.&lt;/p&gt;

&lt;p&gt;I...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;del&gt;still have to learn how to create templates.&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;want better understanding of the available field types.&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;want to find a good CMS package&lt;/li&gt;
&lt;li&gt;want to know if there's the idea of environments in Django (like in symfony)&lt;/li&gt;
&lt;li&gt;want to override .save() on my models&lt;/li&gt;
&lt;li&gt;want to learn how to have self-referencing models (E.g. a hierarchical category)&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>YUI Autocomplete the easy way</title>
   <link href="http://davedash.com/2008/01/27/yui-autocomplete-the-easy-way/"/>
   <updated>2008-01-27T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/27/yui-autocomplete-the-easy-way</id>
   <content type="html">&lt;p&gt;[tags]yui, autocomplete, javascript, jquery, symfony[/tags]&lt;/p&gt;

&lt;p&gt;I'm redoing the UI for &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; and for the Javascript library I am standardizing on is &lt;a href=&quot;http://developer.yahoo.com/yui/&quot;&gt;YUI&lt;/a&gt;.  The trick with &lt;a href=&quot;http://developer.yahoo.com/yui/&quot;&gt;YUI&lt;/a&gt; is that it's very verbose and very configurable.&lt;/p&gt;

&lt;p&gt;Unfortunately to do simple things you have to write quite a lot.  Which, coming from a jQuery background, is not what I'm used to.  Yahoo provides a lot of useful examples for Auto Complete, I'll provide you with another (built in &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, but anything in any server-side language will work).  You'll need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A data source&lt;/li&gt;
&lt;li&gt;Some YUI&lt;/li&gt;
&lt;li&gt;Some javascript of your own&lt;/li&gt;
&lt;li&gt;And some HTML&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;We're building a tool to grab random items from our database and put them into our Autocomplete field.  We also want to capture and id of these items and set them in our form.&lt;/p&gt;

&lt;p&gt;Here we go...&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;Data Source&lt;/h3&gt;

&lt;p&gt;YUI gives you a lot of options with data sources.  In many cases a remote script that returns JSON will be best.  JSON is nice, compact, human debuggable and machine-readable.&lt;/p&gt;

&lt;p&gt;We can make a &lt;code&gt;searchJSON&lt;/code&gt; method that simply takes your database results and wraps them in to a formatted array and then using PHP's &lt;code&gt;json_encode&lt;/code&gt; we can return a nice JSON object.&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public static function searchJSON($phrase, $exact = false, $offset = 0, $max = 10)
    {
        $rs = self::doSearch($phrase, $exact, $offset, $max);

        // Manage the results
        $restaurants = array();
        while ($rs-&gt;next())
        {
          $r                           = self::retrieveByPK($rs-&gt;getInt(1));
            $objs[] = array('Id'=&gt;$rs-&gt;getInt(1), 'Title'=&gt;$r-&gt;getName());
        }

    return json_encode(array('ResultSet' =&gt; array(&quot;Result&quot; =&gt; $objs)));
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;ResultSet&lt;/code&gt; and &lt;code&gt;Result&lt;/code&gt; keys are important as we'll see later.&lt;/p&gt;

&lt;p&gt;We can serve this JSON method very easily in symfony like so:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
  public function executeAjaxList()
  {
    $q    = $this-&gt;getRequestParameter('query');
    $objs = ObjPeer::searchJSON($q);
    return $this-&gt;renderText($objs);
  }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Note that we use &lt;code&gt;renderText&lt;/code&gt; rather than a separate &lt;code&gt;ajaxListSuccess.php&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;YUI&lt;/h3&gt;

&lt;p&gt;There's some &lt;a href=&quot;http://developer.yahoo.com/yui/&quot;&gt;YUI&lt;/a&gt; code that you need.  It's described well in the &lt;a href=&quot;http://developer.yahoo.com/yui/autocomplete/&quot;&gt;Autocomplete documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Add the following to your page:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;http://yui.yahooapis.com/2.4.1/build/yahoo-dom-event/yahoo-dom-event.js&quot; type=&quot;text/javascript
http://yui.yahooapis.com/2.4.1/build/connection/connection-min.js type=&quot;text/javascript
http://yui.yahooapis.com/2.4.1/build/autocomplete/autocomplete-min.js
http://yui.yahooapis.com/2.4.1/build/autocomplete/assets/skins/sam/autocomplete.css
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;These are the bare YUI requirements to get Autocomplete working.&lt;/p&gt;

&lt;h3&gt;Javascript&lt;/h3&gt;

&lt;p&gt;There's a good deal of Javascript that you'll need to provide on your own.  Here's what I wrote:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;javascript&quot;&gt;
    var MA = {};
    MA.autocomplete = function()
    {
      var e = YAHOO.util.Event;
      var w = YAHOO.widget;
  
      return {
        init: function()
        {
           e.onAvailable(&quot;myInput&quot;, this.fnHandler);
        },
        fnHandler: function()
        {
          var rDS = new w.DS_XHR(&quot;/ajax/object/list&quot;, [&quot;ResultSet.Result&quot;,&quot;Title&quot;]);

          rDS.maxCacheEntries    = 60; 
          rDS.queryMatchContains = true;

          var rAC = new w.AutoComplete(&quot;myInput&quot;,&quot;myACContainer&quot;, rDS); 

          rAC.formatResult = function(item, query) 
          {
            return item[1].Title;
          };
      
          rAC.forceSelection           = true; 
          rAC.allowBrowserAutocomplete = false; 
      
          rAC.itemSelectEvent.subscribe(
            function(sType, aArgs) 
            { 
              var data = aArgs[2];
              document.getElementById(&quot;object_id&quot;).value = aArgs[2][1]['Id'];
            }
          ); 
        },
    
      }
    }();

    MA.autocomplete.init();

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;MA&lt;/code&gt; is my own private namespace.  &lt;code&gt;/ajax/object/list&lt;/code&gt; is the url of our data source we defined earlier.  &lt;code&gt;myInput&lt;/code&gt;, &lt;code&gt;myACContainer&lt;/code&gt; and &lt;code&gt;object_id&lt;/code&gt; are all IDs of elements in our DOM which we'll look at next.&lt;/p&gt;

&lt;h3&gt;HTML&lt;/h3&gt;

&lt;p&gt;Okay I went through this backwards, now we have the HTML.&lt;/p&gt;

&lt;p&gt;This is all you need:&lt;/p&gt;

&lt;div&gt;
&lt;textarea name=&quot;code&quot; class=&quot;html&quot;&gt;
    &lt;div id=&quot;myAutoComplete&quot;&gt;
      &lt;?php echo input_tag('myInput',null,'class=text') ?&gt;
      &lt;div id=&quot;myACContainer&quot;&gt;&lt;/div&gt;
      &lt;?php echo input_hidden_tag('object_id') ?&gt;
    &lt;/div&gt;
&lt;/textarea&gt;
&lt;/div&gt;


&lt;h3&gt;The End&lt;/h3&gt;

&lt;p&gt;That's it.  It might seem like a lot, but &lt;a href=&quot;http://developer.yahoo.com/yui/&quot;&gt;YUI&lt;/a&gt; offers a tested working solution that works and is very customizable.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>YUI Autocomplete the easy way</title>
   <link href="http://davedash.com/2008/01/27/yui-autocomplete-the-easy-way/"/>
   <updated>2008-01-27T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/27/yui-autocomplete-the-easy-way</id>
   <content type="html">&lt;p&gt;[tags]yui, autocomplete, javascript, jquery, symfony[/tags]&lt;/p&gt;

&lt;p&gt;I'm redoing the UI for &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; and for the Javascript library I am standardizing on is &lt;a href=&quot;http://developer.yahoo.com/yui/&quot;&gt;YUI&lt;/a&gt;.  The trick with &lt;a href=&quot;http://developer.yahoo.com/yui/&quot;&gt;YUI&lt;/a&gt; is that it's very verbose and very configurable.&lt;/p&gt;

&lt;p&gt;Unfortunately to do simple things you have to write quite a lot.  Which, coming from a jQuery background, is not what I'm used to.  Yahoo provides a lot of useful examples for Auto Complete, I'll provide you with another (built in &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, but anything in any server-side language will work).  You'll need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A data source&lt;/li&gt;
&lt;li&gt;Some YUI&lt;/li&gt;
&lt;li&gt;Some javascript of your own&lt;/li&gt;
&lt;li&gt;And some HTML&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;We're building a tool to grab random items from our database and put them into our Autocomplete field.  We also want to capture and id of these items and set them in our form.&lt;/p&gt;

&lt;p&gt;Here we go...&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;Data Source&lt;/h3&gt;

&lt;p&gt;YUI gives you a lot of options with data sources.  In many cases a remote script that returns JSON will be best.  JSON is nice, compact, human debuggable and machine-readable.&lt;/p&gt;

&lt;p&gt;We can make a &lt;code&gt;searchJSON&lt;/code&gt; method that simply takes your database results and wraps them in to a formatted array and then using PHP's &lt;code&gt;json_encode&lt;/code&gt; we can return a nice JSON object.&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public static function searchJSON($phrase, $exact = false, $offset = 0, $max = 10)
    {
        $rs = self::doSearch($phrase, $exact, $offset, $max);

        // Manage the results
        $restaurants = array();
        while ($rs-&gt;next())
        {
          $r                           = self::retrieveByPK($rs-&gt;getInt(1));
            $objs[] = array('Id'=&gt;$rs-&gt;getInt(1), 'Title'=&gt;$r-&gt;getName());
        }

    return json_encode(array('ResultSet' =&gt; array(&quot;Result&quot; =&gt; $objs)));
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;ResultSet&lt;/code&gt; and &lt;code&gt;Result&lt;/code&gt; keys are important as we'll see later.&lt;/p&gt;

&lt;p&gt;We can serve this JSON method very easily in symfony like so:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
  public function executeAjaxList()
  {
    $q    = $this-&gt;getRequestParameter('query');
    $objs = ObjPeer::searchJSON($q);
    return $this-&gt;renderText($objs);
  }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Note that we use &lt;code&gt;renderText&lt;/code&gt; rather than a separate &lt;code&gt;ajaxListSuccess.php&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;YUI&lt;/h3&gt;

&lt;p&gt;There's some &lt;a href=&quot;http://developer.yahoo.com/yui/&quot;&gt;YUI&lt;/a&gt; code that you need.  It's described well in the &lt;a href=&quot;http://developer.yahoo.com/yui/autocomplete/&quot;&gt;Autocomplete documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Add the following to your page:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;http://yui.yahooapis.com/2.4.1/build/yahoo-dom-event/yahoo-dom-event.js&quot; type=&quot;text/javascript
http://yui.yahooapis.com/2.4.1/build/connection/connection-min.js type=&quot;text/javascript
http://yui.yahooapis.com/2.4.1/build/autocomplete/autocomplete-min.js
http://yui.yahooapis.com/2.4.1/build/autocomplete/assets/skins/sam/autocomplete.css
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;These are the bare YUI requirements to get Autocomplete working.&lt;/p&gt;

&lt;h3&gt;Javascript&lt;/h3&gt;

&lt;p&gt;There's a good deal of Javascript that you'll need to provide on your own.  Here's what I wrote:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;javascript&quot;&gt;
    var MA = {};
    MA.autocomplete = function()
    {
      var e = YAHOO.util.Event;
      var w = YAHOO.widget;
  
      return {
        init: function()
        {
           e.onAvailable(&quot;myInput&quot;, this.fnHandler);
        },
        fnHandler: function()
        {
          var rDS = new w.DS_XHR(&quot;/ajax/object/list&quot;, [&quot;ResultSet.Result&quot;,&quot;Title&quot;]);

          rDS.maxCacheEntries    = 60; 
          rDS.queryMatchContains = true;

          var rAC = new w.AutoComplete(&quot;myInput&quot;,&quot;myACContainer&quot;, rDS); 

          rAC.formatResult = function(item, query) 
          {
            return item[1].Title;
          };
      
          rAC.forceSelection           = true; 
          rAC.allowBrowserAutocomplete = false; 
      
          rAC.itemSelectEvent.subscribe(
            function(sType, aArgs) 
            { 
              var data = aArgs[2];
              document.getElementById(&quot;object_id&quot;).value = aArgs[2][1]['Id'];
            }
          ); 
        },
    
      }
    }();

    MA.autocomplete.init();

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;MA&lt;/code&gt; is my own private namespace.  &lt;code&gt;/ajax/object/list&lt;/code&gt; is the url of our data source we defined earlier.  &lt;code&gt;myInput&lt;/code&gt;, &lt;code&gt;myACContainer&lt;/code&gt; and &lt;code&gt;object_id&lt;/code&gt; are all IDs of elements in our DOM which we'll look at next.&lt;/p&gt;

&lt;h3&gt;HTML&lt;/h3&gt;

&lt;p&gt;Okay I went through this backwards, now we have the HTML.&lt;/p&gt;

&lt;p&gt;This is all you need:&lt;/p&gt;

&lt;div&gt;
&lt;textarea name=&quot;code&quot; class=&quot;html&quot;&gt;
    &lt;div id=&quot;myAutoComplete&quot;&gt;
      &lt;?php echo input_tag('myInput',null,'class=text') ?&gt;
      &lt;div id=&quot;myACContainer&quot;&gt;&lt;/div&gt;
      &lt;?php echo input_hidden_tag('object_id') ?&gt;
    &lt;/div&gt;
&lt;/textarea&gt;
&lt;/div&gt;


&lt;h3&gt;The End&lt;/h3&gt;

&lt;p&gt;That's it.  It might seem like a lot, but &lt;a href=&quot;http://developer.yahoo.com/yui/&quot;&gt;YUI&lt;/a&gt; offers a tested working solution that works and is very customizable.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>python: it begins</title>
   <link href="http://davedash.com/2008/01/26/python-it-begins/"/>
   <updated>2008-01-26T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/26/python-it-begins</id>
   <content type="html">&lt;p&gt;[tags]php, python, symfony, frameworks, programming[/tags]&lt;/p&gt;

&lt;p&gt;The problem I have with python, is I really like what I can do with it, and it makes me hate that I have to use PHP.&lt;/p&gt;

&lt;p&gt;PHP works.  I've used it for over 10 years without problem.  My web development methodology was this: I want to do &lt;code&gt;x&lt;/code&gt;, but I need to first learn &lt;code&gt;y&lt;/code&gt;.  Well one day &lt;code&gt;y&lt;/code&gt; became PHP/FI and then PHP 3 and then PHP 4 and then PHP5.  It was a natural progression.&lt;/p&gt;

&lt;p&gt;I came from a (what was at the time) traditional C/C++/Java background and decided that PHP while amateurish was still a better solution for web development.  A few years later I was proven right by companies like Yahoo! and Facebook.&lt;/p&gt;

&lt;p&gt;I did have my issues, and I worked through them, symfony and other frameworks like it, have made PHP development so much better, but eventually you get let in on little secrets.  Secrets like other scripting languages.&lt;/p&gt;

&lt;p&gt;In the last year I dabbled with ruby, but decided I didn't want to look at rails.  A few people pointed me in the direction of python and Django as an alternative to PHP alternatives.  So I've spent time learning python.&lt;/p&gt;

&lt;p&gt;Yesterday and today I gave myself a real project.  I started hacking away at del.icio.us for a Winter Hack Day at Yahoo!  I would write a few lines of code, and then realize that I can write it a little better a little cleaner using some neat little python constructs.  It's enjoyable in the way that PHP is not.&lt;/p&gt;

&lt;p&gt;It might just be a phase, but I sincerely doubt it.  A lot of people I know who use python love it.  Many have been using it for years (even while I secretly thought, &quot;python? really?&quot;  Well, I'm starting to think they've been onto something.  We'll see where I end up.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Fixing broken PATH_INFO</title>
   <link href="http://davedash.com/2008/01/23/fixing-broken-path_info/"/>
   <updated>2008-01-23T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/23/fixing-broken-path_info</id>
   <content type="html">&lt;p&gt;[tags]php, cgi, path_info, nginx, symfony[/tags]&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; and other applications rely on the server's &lt;code&gt;PATH_INFO&lt;/code&gt; being set properly.  According to &lt;a href=&quot;http://hoohoo.ncsa.uiuc.edu/cgi/env.html&quot;&gt;NCSA&lt;/a&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;The extra path information, as given by the client. In other words, scripts can be accessed by their virtual pathname, followed by extra information at the end of this path. The extra information is sent as `PATH_INFO`. This information *should be decoded by the server* if it comes from a URL before it is passed to the CGI script.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Unfortunately, I use a nonstandard server that doesn't natively support CGI, so everything sent to the FastCGI server is done so via parameters that are usually obtained from the HTTP request, but I can't figure out how to do a &lt;code&gt;urldecode&lt;/code&gt; in my configuration.&lt;/p&gt;

&lt;p&gt;So to workaround this I used the &lt;code&gt;auto_prepend_file&lt;/code&gt; directive in &lt;code&gt;php.ini&lt;/code&gt;.  With OP code caching this shouldn't hurt too much:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;auto_prepend_file = /var/www/pathinfofix.php
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I then added the following script:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php 
$_SERVER['PATH_INFO'] = urldecode($_SERVER['ORIG_PATH_INFO']);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Voila, the &lt;code&gt;PATH_INFO&lt;/code&gt; is in a format that symfony (and any other PHP script that depends on &lt;code&gt;PATH_INFO&lt;/code&gt;) needs.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Fixing broken PATH_INFO</title>
   <link href="http://davedash.com/2008/01/23/fixing-broken-path_info/"/>
   <updated>2008-01-23T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/23/fixing-broken-path_info</id>
   <content type="html">&lt;p&gt;[tags]php, cgi, path_info, nginx, symfony[/tags]&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; and other applications rely on the server's &lt;code&gt;PATH_INFO&lt;/code&gt; being set properly.  According to &lt;a href=&quot;http://hoohoo.ncsa.uiuc.edu/cgi/env.html&quot;&gt;NCSA&lt;/a&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;The extra path information, as given by the client. In other words, scripts can be accessed by their virtual pathname, followed by extra information at the end of this path. The extra information is sent as `PATH_INFO`. This information *should be decoded by the server* if it comes from a URL before it is passed to the CGI script.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Unfortunately, I use a nonstandard server that doesn't natively support CGI, so everything sent to the FastCGI server is done so via parameters that are usually obtained from the HTTP request, but I can't figure out how to do a &lt;code&gt;urldecode&lt;/code&gt; in my configuration.&lt;/p&gt;

&lt;p&gt;So to workaround this I used the &lt;code&gt;auto_prepend_file&lt;/code&gt; directive in &lt;code&gt;php.ini&lt;/code&gt;.  With OP code caching this shouldn't hurt too much:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;auto_prepend_file = /var/www/pathinfofix.php
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I then added the following script:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php 
$_SERVER['PATH_INFO'] = urldecode($_SERVER['ORIG_PATH_INFO']);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Voila, the &lt;code&gt;PATH_INFO&lt;/code&gt; is in a format that symfony (and any other PHP script that depends on &lt;code&gt;PATH_INFO&lt;/code&gt;) needs.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Full Time Web Developers (willing to relocate to the Bay Area)</title>
   <link href="http://davedash.com/2008/01/22/full-time-web-developers-willing-to-relocate-to-the-bay-area/"/>
   <updated>2008-01-22T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/22/full-time-web-developers-willing-to-relocate-to-the-bay-area</id>
   <content type="html">&lt;p&gt;[tags]jobs, yahoo, symfony[/tags]&lt;/p&gt;

&lt;p&gt;If anybody (preferably with symfony experience) would like to work with me full time, please send me an email or leave a comment here.&lt;/p&gt;

&lt;p&gt;I'm preferably looking for someone with symfony experience that's great with CSS and JS, especially with YUI.&lt;/p&gt;

&lt;p&gt;-d&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Full Time Web Developers (willing to relocate to the Bay Area)</title>
   <link href="http://davedash.com/2008/01/22/full-time-web-developers-willing-to-relocate-to-the-bay-area/"/>
   <updated>2008-01-22T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/22/full-time-web-developers-willing-to-relocate-to-the-bay-area</id>
   <content type="html">&lt;p&gt;[tags]jobs, yahoo, symfony[/tags]&lt;/p&gt;

&lt;p&gt;If anybody (preferably with symfony experience) would like to work with me full time, please send me an email or leave a comment here.&lt;/p&gt;

&lt;p&gt;I'm preferably looking for someone with symfony experience that's great with CSS and JS, especially with YUI.&lt;/p&gt;

&lt;p&gt;-d&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>ddAccessibleFormPlugin updated</title>
   <link href="http://davedash.com/2008/01/22/ddaccessibleformplugin-updated/"/>
   <updated>2008-01-22T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/22/ddaccessibleformplugin-updated</id>
   <content type="html">&lt;p&gt;[tags]
yui, jquery, javascript, forms, symfony, plugin
[/tags]&lt;/p&gt;

&lt;p&gt;I updated the &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; plugin for &lt;a href=&quot;http://alistapart.com/articles/prettyaccessibleforms&quot;&gt;Pretty Accesible Forms&lt;/a&gt; available &lt;a href=&quot;http://svn.symfony-project.com/plugins/ddAccessibleFormPlugin&quot;&gt;via svn&lt;/a&gt;.  Feel free to read about it on &lt;a href=&quot;http://trac.symfony-project.com/wiki/plugins/ddAccesibleFormPlugin&quot;&gt;trac&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately jQuery the Javascript library I know and love wasn't helping me in the auto-complete department.  While a very good plugin exists, it wasn't working as well as I'd like.&lt;/p&gt;

&lt;p&gt;So I looked toward YUI.  It's what we use at my work (duh) and I wanted to see what all the fuss was about.  So I rewrote the handful of jQuery scripts on &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; in YUI.  It didn't take too long despite the verbose syntax.&lt;/p&gt;

&lt;p&gt;Unfortunately that meant the accessible form plugin which I depend on needed to be updated as well.  So now you've got a choice between jQuery and YUI.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>nginx and symfony</title>
   <link href="http://davedash.com/2008/01/20/nginx-and-symfony/"/>
   <updated>2008-01-20T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/20/nginx-and-symfony</id>
   <content type="html">&lt;p&gt;[tags]nginx, server, symfony[/tags]&lt;/p&gt;

&lt;p&gt;I almost gave up on nginx and was going to settle on lighttpd or Apache, but I decided to check the symfony list and in minutes Kiril Angov (Kupokomapa) answered with a working nginx configuration.  This is why I like the symfony community :)&lt;/p&gt;

&lt;p&gt;Here's what works (Note: This requires a running FastCGI server):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;server {
  listen       80;
  server_name  mysite.com *;
  root /var/www/mysite.com/web;
  index  index.php;

  charset utf-8;

  location / {
    # If the file exists as a static file serve it directly without
    # running all the other rewite tests on it
    if (-f $request_filename) {
      expires max; 
      break; 
    }

    if ($request_filename !~ &quot;\.(js|htc|ico|gif|jpg|png|css)$&quot;) {
      rewrite ^(.*) /index.php last;
    }
  }

  location ~ \.php($|/) {
    set  $script     $uri;
    set  $path_info  &quot;&quot;;

    if ($uri ~ &quot;^(.+\.php)(/.+)&quot;) {
      set  $script     $1;
      set  $path_info  $2;
    }

    fastcgi_pass   127.0.0.1:9000;

    include /etc/nginx/fastcgi_params;

    fastcgi_param  SCRIPT_FILENAME  /var/www/mysite.com/web$script;
    fastcgi_param  PATH_INFO        $path_info;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This works in my Ubuntu instance of nginx.  Other customziations to this might need to occur, but it's fairly solid so far.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>nginx and symfony</title>
   <link href="http://davedash.com/2008/01/20/nginx-and-symfony/"/>
   <updated>2008-01-20T00:00:00-08:00</updated>
   <id>http://davedash.com/2008/01/20/nginx-and-symfony</id>
   <content type="html">&lt;p&gt;[tags]nginx, server, symfony[/tags]&lt;/p&gt;

&lt;p&gt;I almost gave up on nginx and was going to settle on lighttpd or Apache, but I decided to check the symfony list and in minutes Kiril Angov (Kupokomapa) answered with a working nginx configuration.  This is why I like the symfony community :)&lt;/p&gt;

&lt;p&gt;Here's what works (Note: This requires a running FastCGI server):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;server {
  listen       80;
  server_name  mysite.com *;
  root /var/www/mysite.com/web;
  index  index.php;

  charset utf-8;

  location / {
    # If the file exists as a static file serve it directly without
    # running all the other rewite tests on it
    if (-f $request_filename) {
      expires max; 
      break; 
    }

    if ($request_filename !~ &quot;\.(js|htc|ico|gif|jpg|png|css)$&quot;) {
      rewrite ^(.*) /index.php last;
    }
  }

  location ~ \.php($|/) {
    set  $script     $uri;
    set  $path_info  &quot;&quot;;

    if ($uri ~ &quot;^(.+\.php)(/.+)&quot;) {
      set  $script     $1;
      set  $path_info  $2;
    }

    fastcgi_pass   127.0.0.1:9000;

    include /etc/nginx/fastcgi_params;

    fastcgi_param  SCRIPT_FILENAME  /var/www/mysite.com/web$script;
    fastcgi_param  PATH_INFO        $path_info;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This works in my Ubuntu instance of nginx.  Other customziations to this might need to occur, but it's fairly solid so far.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Better than ORM Object Persistence</title>
   <link href="http://davedash.com/2007/12/20/better-than-orm-object-persistence/"/>
   <updated>2007-12-20T00:00:00-08:00</updated>
   <id>http://davedash.com/2007/12/20/better-than-orm-object-persistence</id>
   <content type="html">&lt;p&gt; After talking to people about the benefits and disadvantages of various ORMs... and reading up a little on non RDMBSs like &lt;a href=&quot;http://www.amazon.com/gp/browse.html?node=342335011&quot;&gt;Amazon SimpleDB&lt;/a&gt; I came to the realization that ORM is really a hack to get RDBMSs to work as a storage for objects.&lt;/p&gt;

&lt;p&gt;I'm being liberal with the term hack.  It really does work for a lot of situations, but it's not very elegant.  The workflow is more or less this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You create a database.&lt;/li&gt;
&lt;li&gt;You create some objects&lt;/li&gt;
&lt;li&gt;You define tables to store attributes of objects&lt;/li&gt;
&lt;li&gt;You establish a mapping&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;There's a lot that goes into database definition.  It would be nice to breakout from this line of thinking and do things a bit differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create database&lt;/li&gt;
&lt;li&gt;Save named (e.g. Person, Place, Log, Restaurant, Rating, Review) serialized objects to the database.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Let the database learn from the saving of the object how to define the type.  In fact, it should be flexible and let us have mixmatched types, etc.&lt;/p&gt;

&lt;p&gt;Let the database index everything, and keep the indexes up to date, but prioritize it based on usage.  In other words if we usually query a database of Persons in order by a field called lastname, then make sure that index on lastname is always at hand.  We should be able to query this data back out of storage based on how we stored it.&lt;/p&gt;

&lt;p&gt;We should also be able to reach other objects in the database in a similar manner.&lt;/p&gt;

&lt;p&gt;The key here is letting the database layer do the heavy-thinking about what to do with serialized data, versus having some middle layer take care of it.&lt;/p&gt;

&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;so I might just be naive about data and databases.  But if this idea is worthwhile and some database people can validate me, I'd be willing to work on it.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>goto() Bash scripts for changing directories</title>
   <link href="http://davedash.com/2007/11/27/goto-bash-scripts-for-changing-directories/"/>
   <updated>2007-11-27T00:00:00-08:00</updated>
   <id>http://davedash.com/2007/11/27/goto-bash-scripts-for-changing-directories</id>
   <content type="html">&lt;p&gt;So one of the downsides to symfony is traversing the file system.  Lately I've had to do this a lot so I decided to write some &lt;code&gt;bash&lt;/code&gt; functions to make this easier.  Here's one I call &lt;code&gt;goto&lt;/code&gt; which works as such:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;`goto` filename.ext
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Feel free to make this better, or if there exists a built-in that I know nothing about tell me about it.&lt;/p&gt;

&lt;p&gt;Here's the function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;goto () {
  FILE=`find . -iname $1|head -1`
  DIR=`dirname $FILE`
  cd $DIR
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Update&lt;/h3&gt;

&lt;p&gt;I changed the function to take care of this syntax:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;goto default/../actions.class.php&lt;/code&gt; (amongst other things) will now find &lt;code&gt;app/frontend/modules/default/actions&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;goto () {
    FORMATTED_STR=`echo $1|sed -e 's|/../|/*/|g'`
    FILE=`find . -ipath *${FORMATTED_STR}*|head -1`
    DIR=`dirname $FILE`
    cd $DIR
}
&lt;/code&gt;&lt;/pre&gt;
</content>
 </entry>
 
 <entry>
   <title>goto() Bash scripts for changing directories</title>
   <link href="http://davedash.com/2007/11/27/goto-bash-scripts-for-changing-directories/"/>
   <updated>2007-11-27T00:00:00-08:00</updated>
   <id>http://davedash.com/2007/11/27/goto-bash-scripts-for-changing-directories</id>
   <content type="html">&lt;p&gt;So one of the downsides to symfony is traversing the file system.  Lately I've had to do this a lot so I decided to write some &lt;code&gt;bash&lt;/code&gt; functions to make this easier.  Here's one I call &lt;code&gt;goto&lt;/code&gt; which works as such:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;`goto` filename.ext
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Feel free to make this better, or if there exists a built-in that I know nothing about tell me about it.&lt;/p&gt;

&lt;p&gt;Here's the function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;goto () {
  FILE=`find . -iname $1|head -1`
  DIR=`dirname $FILE`
  cd $DIR
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Update&lt;/h3&gt;

&lt;p&gt;I changed the function to take care of this syntax:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;goto default/../actions.class.php&lt;/code&gt; (amongst other things) will now find &lt;code&gt;app/frontend/modules/default/actions&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;goto () {
    FORMATTED_STR=`echo $1|sed -e 's|/../|/*/|g'`
    FILE=`find . -ipath *${FORMATTED_STR}*|head -1`
    DIR=`dirname $FILE`
    cd $DIR
}
&lt;/code&gt;&lt;/pre&gt;
</content>
 </entry>
 
 <entry>
   <title>ddJQueryCalendarPlugin plugin for symfony released</title>
   <link href="http://davedash.com/2007/09/20/ddjquerycalendarplugin-plugin-for-symfony-released/"/>
   <updated>2007-09-20T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/09/20/ddjquerycalendarplugin-plugin-for-symfony-released</id>
   <content type="html">&lt;p&gt;[tags]JQuery, jq, javascript, js, JQuery Calendar, calendar, symfony, plugin[/tags]&lt;/p&gt;

&lt;p&gt;I released a new symfony plugin today for the &lt;a href=&quot;http://marcgrabanski.com/code/jquery-calendar/&quot;&gt;jQuery Calendar&lt;/a&gt; available &lt;a href=&quot;http://svn.symfony-project.com/plugins/ddJQueryCalendar&quot;&gt;via svn&lt;/a&gt;.  Feel free to read about it on &lt;a href=&quot;http://trac.symfony-project.com/trac/wiki/ddJQueryCalendar&quot;&gt;trac&lt;/a&gt;.  This adds a clever calendar widget that can trigger events.  Very handy for viewing an events calendar.&lt;/p&gt;

&lt;p&gt;I'll publish a link to this plugin in action in the following weeks.  Enjoy.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>ddJQueryCalendarPlugin plugin for symfony released</title>
   <link href="http://davedash.com/2007/09/20/ddjquerycalendarplugin-plugin-for-symfony-released/"/>
   <updated>2007-09-20T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/09/20/ddjquerycalendarplugin-plugin-for-symfony-released</id>
   <content type="html">&lt;p&gt;[tags]JQuery, jq, javascript, js, JQuery Calendar, calendar, symfony, plugin[/tags]&lt;/p&gt;

&lt;p&gt;I released a new symfony plugin today for the &lt;a href=&quot;http://marcgrabanski.com/code/jquery-calendar/&quot;&gt;jQuery Calendar&lt;/a&gt; available &lt;a href=&quot;http://svn.symfony-project.com/plugins/ddJQueryCalendar&quot;&gt;via svn&lt;/a&gt;.  Feel free to read about it on &lt;a href=&quot;http://trac.symfony-project.com/trac/wiki/ddJQueryCalendar&quot;&gt;trac&lt;/a&gt;.  This adds a clever calendar widget that can trigger events.  Very handy for viewing an events calendar.&lt;/p&gt;

&lt;p&gt;I'll publish a link to this plugin in action in the following weeks.  Enjoy.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>symfonyCamp </title>
   <link href="http://davedash.com/2007/09/11/symfonycamp/"/>
   <updated>2007-09-11T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/09/11/symfonycamp</id>
   <content type="html">&lt;p&gt;[tags]symfony, symfonyCamp, sensio, dop, zend search lucene, zsl[/tags]&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;photoright&quot;&gt;
&lt;img src=&quot;http://farm2.static.flickr.com/1096/1354502708_97f225a078_m.jpg?v=s&quot; alt=&quot;Tents&quot; /&gt;
&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Well Katie and I are back from &lt;a href=&quot;http://symfonycamp.com/&quot;&gt;symfonyCamp&lt;/a&gt; and it was great.  I opted to socialize on the business day, except to hear Fabien Potencier's brief overview of what's to come in symfony 1.1/2.0.&lt;/p&gt;

&lt;p&gt;I personally know only a handful of adept symfony developers, so going to camp it was nice to see 50 people or so who knew symfony to varying degrees.  I am a fan of the small successes, such as &lt;a href=&quot;http://www.tempus-vivit.net/&quot;&gt;Fabian Lange's historical reenactment site&lt;/a&gt; which paid the way for two developers to attend the conference.&lt;/p&gt;

&lt;p&gt;One of the most interesting talks to hear was Fabien's overview of symfony 2.0.  It was a 12-step process from going to vanilla PHP to building a strong framework in about 200 lines.  A leaner more robust symfony sounds very appealing, it's also what appeals to me about being a developer.  Every developer builds off of existing technologies and is able to create something great.  I frequently have to dig into the symfony core code to see how things &quot;really&quot; work and everything is simple, easy to follow building blocks.  Effectively giving us a nice framework.  symfony 2.0 seems to be a leaner more flexible framework.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;photoleft&quot;&gt;
&lt;img src=&quot;http://farm2.static.flickr.com/1283/1341189131_1bfe60a945_m.jpg&quot; alt=&quot;Zend talk&quot; /&gt;
&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Later I spoke on Zend Search Lucene as well as Ajax.  Both are somewhat difficult to speak on for 45 minutes.  Zend Search Lucene really only takes 15 minutes to explain, and maybe just as long to implement.  Sure it can be tweaked quite a bit, but it's straightforward - that's the point.&lt;/p&gt;

&lt;p&gt;Ajax on the other hand is hard to explain in terms of symfony.  Sure there's a helper layer, but the Javascript layer is very independent of the PHP layer.  Anything can support Ajax.  So I tried to cover not just the standard helpers, but show a few demos and how easy it is with the helper system.  Unfortunately I don't really code this way, I try to use UJS and the jQuery plugins with new work, but that would move the talk to a more advanced topic.&lt;/p&gt;

&lt;p&gt;The next day we ended up cleaning up the symfony project.  We split into teams to take care of some house-work.  Some people cleaned up the wiki.  Some cleaned up tickets, some wrote new modules for the site.  Our team worked on plugins and it went rather well.  We closed a number of tickets, created a good deal of patches (which I have yet to apply), but overall the plugins are all a bit better.&lt;/p&gt;

&lt;p&gt;Overall everything was great.  &lt;a href=&quot;http://dop.nu/&quot;&gt;Dutch Open Projects&lt;/a&gt; was great, especially Stefan for arranging so much, Guido for making sure everyone was comfortable and Floris for the great food.&lt;/p&gt;

&lt;p&gt;You can also read &lt;a href=&quot;http://www.symfony-project.com/blog/2007/09/07/symfony-camp&quot;&gt;Fabien's overview&lt;/a&gt; of the camp.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>symfonyCamp </title>
   <link href="http://davedash.com/2007/09/11/symfonycamp/"/>
   <updated>2007-09-11T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/09/11/symfonycamp</id>
   <content type="html">&lt;p&gt;[tags]symfony, symfonyCamp, sensio, dop, zend search lucene, zsl[/tags]&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;photoright&quot;&gt;
&lt;img src=&quot;http://farm2.static.flickr.com/1096/1354502708_97f225a078_m.jpg?v=s&quot; alt=&quot;Tents&quot; /&gt;
&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Well Katie and I are back from &lt;a href=&quot;http://symfonycamp.com/&quot;&gt;symfonyCamp&lt;/a&gt; and it was great.  I opted to socialize on the business day, except to hear Fabien Potencier's brief overview of what's to come in symfony 1.1/2.0.&lt;/p&gt;

&lt;p&gt;I personally know only a handful of adept symfony developers, so going to camp it was nice to see 50 people or so who knew symfony to varying degrees.  I am a fan of the small successes, such as &lt;a href=&quot;http://www.tempus-vivit.net/&quot;&gt;Fabian Lange's historical reenactment site&lt;/a&gt; which paid the way for two developers to attend the conference.&lt;/p&gt;

&lt;p&gt;One of the most interesting talks to hear was Fabien's overview of symfony 2.0.  It was a 12-step process from going to vanilla PHP to building a strong framework in about 200 lines.  A leaner more robust symfony sounds very appealing, it's also what appeals to me about being a developer.  Every developer builds off of existing technologies and is able to create something great.  I frequently have to dig into the symfony core code to see how things &quot;really&quot; work and everything is simple, easy to follow building blocks.  Effectively giving us a nice framework.  symfony 2.0 seems to be a leaner more flexible framework.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;photoleft&quot;&gt;
&lt;img src=&quot;http://farm2.static.flickr.com/1283/1341189131_1bfe60a945_m.jpg&quot; alt=&quot;Zend talk&quot; /&gt;
&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Later I spoke on Zend Search Lucene as well as Ajax.  Both are somewhat difficult to speak on for 45 minutes.  Zend Search Lucene really only takes 15 minutes to explain, and maybe just as long to implement.  Sure it can be tweaked quite a bit, but it's straightforward - that's the point.&lt;/p&gt;

&lt;p&gt;Ajax on the other hand is hard to explain in terms of symfony.  Sure there's a helper layer, but the Javascript layer is very independent of the PHP layer.  Anything can support Ajax.  So I tried to cover not just the standard helpers, but show a few demos and how easy it is with the helper system.  Unfortunately I don't really code this way, I try to use UJS and the jQuery plugins with new work, but that would move the talk to a more advanced topic.&lt;/p&gt;

&lt;p&gt;The next day we ended up cleaning up the symfony project.  We split into teams to take care of some house-work.  Some people cleaned up the wiki.  Some cleaned up tickets, some wrote new modules for the site.  Our team worked on plugins and it went rather well.  We closed a number of tickets, created a good deal of patches (which I have yet to apply), but overall the plugins are all a bit better.&lt;/p&gt;

&lt;p&gt;Overall everything was great.  &lt;a href=&quot;http://dop.nu/&quot;&gt;Dutch Open Projects&lt;/a&gt; was great, especially Stefan for arranging so much, Guido for making sure everyone was comfortable and Floris for the great food.&lt;/p&gt;

&lt;p&gt;You can also read &lt;a href=&quot;http://www.symfony-project.com/blog/2007/09/07/symfony-camp&quot;&gt;Fabien's overview&lt;/a&gt; of the camp.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>resetting CSS list items</title>
   <link href="http://davedash.com/2007/09/01/resetting-css-list-items/"/>
   <updated>2007-09-01T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/09/01/resetting-css-list-items</id>
   <content type="html">&lt;p&gt;[tags]css, symfony, blueprint, yui[/tags]&lt;/p&gt;

&lt;p&gt;Call me a purist, but I really believe that Blueprint should go the extra step as YUI (note: I'm a YUI CSS user dabbling in Blueprint) does and reset list items as well.  It's all about how you use your elements and what you consider as resetting, but I feel that a bullet added to a list is something that should be removed in a proper reset.css file.&lt;/p&gt;

&lt;p&gt;Different browsers are going to have their own ways of rendering bullets and really the developer should explicitly inform us how they would like their lists rendered.&lt;/p&gt;

&lt;p&gt;Personally, most of my list items are lists of links as in menus, which require no bullets usually.  This is probably the source of my bias.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>sfBlueprintPlugin: Train of thought development</title>
   <link href="http://davedash.com/2007/08/30/sfblueprintplugin-train-of-thought-development/"/>
   <updated>2007-08-30T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/30/sfblueprintplugin-train-of-thought-development</id>
   <content type="html">&lt;p&gt;I've been creating some dummy projects for my presentations at &lt;a href=&quot;http://symfonycamp.com/&quot;&gt;SymfonyCamp&lt;/a&gt; and decided now would be a good time to learn using the Blueprint CSS framework.  It's a bit different than YUI which I've been using heavily, but it has some potential.&lt;/p&gt;

&lt;p&gt;Of course, instead of just downloading the framework, I made &lt;a href=&quot;http://trac.symfony-project.com/trac/browser/plugins/sfBlueprintPlugin&quot;&gt;a plugin&lt;/a&gt; for symfony.  It's not much yet, but eventually I'll throw in some helpers.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

&lt;p&gt;[tags]css, symfony, blueprint, plugins, symfony camp, camp, yui[/tags]&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>sfBlueprintPlugin: Train of thought development</title>
   <link href="http://davedash.com/2007/08/30/sfblueprintplugin-train-of-thought-development/"/>
   <updated>2007-08-30T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/30/sfblueprintplugin-train-of-thought-development</id>
   <content type="html">&lt;p&gt;I've been creating some dummy projects for my presentations at &lt;a href=&quot;http://symfonycamp.com/&quot;&gt;SymfonyCamp&lt;/a&gt; and decided now would be a good time to learn using the Blueprint CSS framework.  It's a bit different than YUI which I've been using heavily, but it has some potential.&lt;/p&gt;

&lt;p&gt;Of course, instead of just downloading the framework, I made &lt;a href=&quot;http://trac.symfony-project.com/trac/browser/plugins/sfBlueprintPlugin&quot;&gt;a plugin&lt;/a&gt; for symfony.  It's not much yet, but eventually I'll throw in some helpers.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

&lt;p&gt;[tags]css, symfony, blueprint, plugins, symfony camp, camp, yui[/tags]&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Affordable IM/Phone-based consulting</title>
   <link href="http://davedash.com/2007/08/20/affordable-imphone-based-consulting/"/>
   <updated>2007-08-20T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/20/affordable-imphone-based-consulting</id>
   <content type="html">&lt;p&gt;[tags]symfony, spindrop, services, consulting[/tags]&lt;/p&gt;

&lt;p&gt;I'm now available for chat-based consulting.  If you are stuck in a rut and find yourself spending a lot of time in chat rooms or on forums to no avail trying to understand &lt;strong&gt;symfony, php, or javascript (AJAX)&lt;/strong&gt;, this is the solution for you.&lt;/p&gt;

&lt;p&gt;I can give you expert help on best practices and get you out of snags quickly.  The types of problems I can help you solve are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;symfony/PHP white-screens-of-death&lt;/li&gt;
&lt;li&gt;other 404/500 errors&lt;/li&gt;
&lt;li&gt;symfony exceptions&lt;/li&gt;
&lt;li&gt;best practices&lt;/li&gt;
&lt;li&gt;apache configuration&lt;/li&gt;
&lt;li&gt;AJAX/Javascript questions&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;I'm available by chat, or email or phone during pre-arranged times.  I can even remote into  your server via VNC and show you (literally) how to fix your problems.&lt;/p&gt;

&lt;p&gt;If you're interested in this service please &lt;a href=&quot;http://spindrop.us/contact/&quot;&gt;contact me&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Affordable IM/Phone-based consulting</title>
   <link href="http://davedash.com/2007/08/20/affordable-imphone-based-consulting/"/>
   <updated>2007-08-20T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/20/affordable-imphone-based-consulting</id>
   <content type="html">&lt;p&gt;[tags]symfony, spindrop, services, consulting[/tags]&lt;/p&gt;

&lt;p&gt;I'm now available for chat-based consulting.  If you are stuck in a rut and find yourself spending a lot of time in chat rooms or on forums to no avail trying to understand &lt;strong&gt;symfony, php, or javascript (AJAX)&lt;/strong&gt;, this is the solution for you.&lt;/p&gt;

&lt;p&gt;I can give you expert help on best practices and get you out of snags quickly.  The types of problems I can help you solve are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;symfony/PHP white-screens-of-death&lt;/li&gt;
&lt;li&gt;other 404/500 errors&lt;/li&gt;
&lt;li&gt;symfony exceptions&lt;/li&gt;
&lt;li&gt;best practices&lt;/li&gt;
&lt;li&gt;apache configuration&lt;/li&gt;
&lt;li&gt;AJAX/Javascript questions&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;I'm available by chat, or email or phone during pre-arranged times.  I can even remote into  your server via VNC and show you (literally) how to fix your problems.&lt;/p&gt;

&lt;p&gt;If you're interested in this service please &lt;a href=&quot;http://spindrop.us/contact/&quot;&gt;contact me&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Symfony Camp: Ajax and Zend, what would you like to know?</title>
   <link href="http://davedash.com/2007/08/16/symfony-camp-ajax-and-zend-what-would-you-like-to-know/"/>
   <updated>2007-08-16T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/16/symfony-camp-ajax-and-zend-what-would-you-like-to-know</id>
   <content type="html">&lt;p&gt;[tags]symfonyCamp, symfony, netherlands, ajax, zend search lucene, zsl, jquery[/tags]&lt;/p&gt;

&lt;p&gt;I've been asked to speak at &lt;a href=&quot;http://www.symfonycamp.com/&quot;&gt;SymfonyCamp&lt;/a&gt; (&lt;code&gt;symfony['camp']&lt;/code&gt;) next month (you should all go if you can) and I thought I'd present as well as I could on Ajax and the Zend Framework Bridge (including Zend Search Lucene).&lt;/p&gt;

&lt;p&gt;If you're attending the camp and/or would like to hear about these topics please let me know any specific questions you might have about &quot;&lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; and Ajax&quot; and &quot;symfony and Zend&quot; and I'll try to address them in my presentations.&lt;/p&gt;

&lt;p&gt;If you are unable to go fear not, I'll try to post my notes on this site.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Symfony Camp: Ajax and Zend, what would you like to know?</title>
   <link href="http://davedash.com/2007/08/16/symfony-camp-ajax-and-zend-what-would-you-like-to-know/"/>
   <updated>2007-08-16T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/16/symfony-camp-ajax-and-zend-what-would-you-like-to-know</id>
   <content type="html">&lt;p&gt;[tags]symfonyCamp, symfony, netherlands, ajax, zend search lucene, zsl, jquery[/tags]&lt;/p&gt;

&lt;p&gt;I've been asked to speak at &lt;a href=&quot;http://www.symfonycamp.com/&quot;&gt;SymfonyCamp&lt;/a&gt; (&lt;code&gt;symfony['camp']&lt;/code&gt;) next month (you should all go if you can) and I thought I'd present as well as I could on Ajax and the Zend Framework Bridge (including Zend Search Lucene).&lt;/p&gt;

&lt;p&gt;If you're attending the camp and/or would like to hear about these topics please let me know any specific questions you might have about &quot;&lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; and Ajax&quot; and &quot;symfony and Zend&quot; and I'll try to address them in my presentations.&lt;/p&gt;

&lt;p&gt;If you are unable to go fear not, I'll try to post my notes on this site.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>symfony and the .htaccess file</title>
   <link href="http://davedash.com/2007/08/15/symfony-and-the-htaccess-file/"/>
   <updated>2007-08-15T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/15/symfony-and-the-htaccess-file</id>
   <content type="html">&lt;p&gt;One performance boost that can be garnered from a symfony app (or any app for
that matter) is disabling &lt;code&gt;.htaccess&lt;/code&gt;.  &lt;code&gt;.htaccess&lt;/code&gt; does not need to be parsed
on each visit to your app.  Disabling &lt;code&gt;.htaccess&lt;/code&gt; is trivial in your
VirtualHost or another relevant part of your apache configuration place:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;AllowOverride None
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;symfony does require the Rewrite recipes in .htaccess to properly give you
pretty urls, so place those statements right after &lt;code&gt;AllowOverride None&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In my specific case I used this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;VirtualHost *&amp;gt;
  ServerName contest.spindrop.sf
  DocumentRoot    /Users/davedash/Sites/spindrop/web/
  DirectoryIndex  frontend_dev.php

  &amp;lt;Directory /Users/davedash/Sites/spindrop/web/&amp;gt;
    Options Indexes FollowSymLinks MultiViews
    AllowOverride None
    &amp;lt;IfModule mod_rewrite.c&amp;gt;
      RewriteEngine On

      RewriteCond %{REQUEST_URI} \..+$
      RewriteCond %{REQUEST_URI} !\.html$
      RewriteRule .* - [L]

      RewriteRule ^$ index.html [QSA]
      RewriteRule ^([^.]+)$ $1.html [QSA]
      RewriteCond %{REQUEST_FILENAME} !-f

      RewriteRule ^(.*)$ frontend_dev.php [QSA,L]
    &amp;lt;/IfModule&amp;gt;
  &amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now all these rewriting rules are loaded when the server is restarted and the
&lt;code&gt;.htaccess&lt;/code&gt; is not examined upon each request.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>symfony + mod_rewrite: moving smoothly from development to production environments</title>
   <link href="http://davedash.com/2007/08/14/symfony-mod_rewrite-moving-smoothly-from-development-to-production-environments/"/>
   <updated>2007-08-14T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/14/symfony-mod_rewrite-moving-smoothly-from-development-to-production-environments</id>
   <content type="html">&lt;p&gt;I think a common problem for some symfony developers that aren't too familiar
with Apache's Mod Rewrite is when they move from a development environment that
uses an explicit controller (e.g. &lt;code&gt;frontend_dev.php&lt;/code&gt; is requested from the
server explicitly) to their production app which implicitly calls &lt;code&gt;index.php&lt;/code&gt;
(e.g. '/' or some other route is passed to &lt;code&gt;index.php&lt;/code&gt;) things stop working.&lt;/p&gt;

&lt;p&gt;This is probably a &lt;code&gt;mod_rewrite&lt;/code&gt; issue.  Mod Rewrite handles implicitly
directing (almost) all traffic to &lt;code&gt;index.php&lt;/code&gt; in a symfony application.&lt;/p&gt;

&lt;p&gt;The default &lt;code&gt;.htaccess&lt;/code&gt; for symfony is defined as such:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Options +FollowSymLinks +ExecCGI

&amp;lt;IfModule mod_rewrite.c&amp;gt;
  RewriteEngine On

  # uncomment the following line, if you are having trouble
  # getting no_script_name to work
  #RewriteBase /

  # we skip all files with .something
  RewriteCond %{REQUEST_URI} \..+$
  RewriteCond %{REQUEST_URI} !\.html$
  RewriteRule .* - [L]

  # we check if the .html version is here (caching)
  RewriteRule ^$ index.html [QSA]
  RewriteRule ^([^.]+)$ $1.html [QSA]
  RewriteCond %{REQUEST_FILENAME} !-f

  # no, so we redirect to our front web controller
  RewriteRule ^(.*)$ index.php [QSA,L]
&amp;lt;/IfModule&amp;gt;

# big crash from our front web controller
ErrorDocument 500 &quot;&amp;lt;h2&amp;gt;Application error&amp;lt;/h2&amp;gt;symfony application failed to start properly&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As you can see if the &lt;code&gt;mod_rewrite.c&lt;/code&gt; module is not enabled this will not work.&lt;/p&gt;

&lt;p&gt;Luckily most packages of Apache have the module, it just needs to be enabled by uncommenting the right line of &lt;code&gt;.htaccess&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;LoadModule rewrite_module     libexec/httpd/mod_rewrite.so
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Restarting apache should give you full rewrite control.  This is fairly basic Apache administration, but for a lot of people symfony is their first foray into URL rewriting.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>FBML and CSS background images</title>
   <link href="http://davedash.com/2007/08/11/fbml-and-css-background-images/"/>
   <updated>2007-08-11T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/11/fbml-and-css-background-images</id>
   <content type="html">&lt;p&gt;I've been tediously building out the new &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; app for Facebook and I ran into some problem when I tried to embed this CSS:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;css&quot;&gt;
input.openid_login {
            background: url(&quot;http://openid.net/login-bg.gif&quot;) no-repeat #fff;
   background-position: 0 50%;
          padding-left: 18px;
}
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The CSS worked fine, just now background image.  The fix is mentioned in &lt;a href=&quot;http://www.facebook.com/topic.php?uid=2205007948&amp;amp;topic=5140&amp;amp;post=50818&amp;amp;pwstdfy=0434dec4ce75745f05b7396611234b7c#post50818&quot;&gt;this post&lt;/a&gt; which is to repeat the &lt;code&gt;background&lt;/code&gt; statement:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;css&quot;&gt;
input.openid_login {
            background: url(&quot;http://openid.net/login-bg.gif&quot;) no-repeat #fff;
            background: url(&quot;http://openid.net/login-bg.gif&quot;) no-repeat #fff;
   background-position: 0 50%;
          padding-left: 18px;
}
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Voila, it works.  Hopefully this hack won't be necessary in later versions of the Platform.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>FBML and embedded CMS</title>
   <link href="http://davedash.com/2007/08/04/fbml-and-embedded-cms/"/>
   <updated>2007-08-04T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/04/fbml-and-embedded-cms</id>
   <content type="html">&lt;p&gt;[tags]fbml, css, reviewsby.us, partials, symfony, sfFacebookPlatformPlugin[/tags]&lt;/p&gt;

&lt;p&gt;One problem of going the FBML route&lt;sup id=&quot;#fbmlcss_fnr_1&quot;&gt;&lt;a href=&quot;#fbmlcss_fn_1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; is CSS styles.  You can't link to external style sheets so you need to embed everything.&lt;/p&gt;

&lt;p&gt;I took the liberty of using a partial that contains all the useful CSS that I use in our app.  Now we can just embed it in our layout by doing:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php include_partial('sfFacebook/css');?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The following classes are useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.app_content&lt;/code&gt; is the div surrounding the main content of the page.  It gives the canvas some padding (actually it gives itself some margin as not to butt-up against the canvas.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.box&lt;/code&gt; this defines the classic facebook box with a dark blue border at the top and with headers that have the lighter blue background and dark blue text.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.box .header&lt;/code&gt; the header for the box described above.  Use an &lt;code&gt;h2&lt;/code&gt; for the title.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.box .content&lt;/code&gt; the main content section of a box, has a bit of padding.&lt;/li&gt;
&lt;/ul&gt;


&lt;div class=&quot;footnotes&quot;&gt;
&lt;ol&gt;
&lt;li id=&quot;fbmlcss_fn_1&quot;&gt;I'm not completely convinced that the
&lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; app for facebook should be using FBML.

&lt;a href=&quot;#fbmlcss_fnr_1&quot; class=&quot;footnoteBackLink&quot;  title=&quot;Jump back to footnote  in the text.&quot;&gt;&amp;#8617;&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>FBML and embedded CMS</title>
   <link href="http://davedash.com/2007/08/04/fbml-and-embedded-cms/"/>
   <updated>2007-08-04T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/08/04/fbml-and-embedded-cms</id>
   <content type="html">&lt;p&gt;[tags]fbml, css, reviewsby.us, partials, symfony, sfFacebookPlatformPlugin[/tags]&lt;/p&gt;

&lt;p&gt;One problem of going the FBML route&lt;sup id=&quot;#fbmlcss_fnr_1&quot;&gt;&lt;a href=&quot;#fbmlcss_fn_1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; is CSS styles.  You can't link to external style sheets so you need to embed everything.&lt;/p&gt;

&lt;p&gt;I took the liberty of using a partial that contains all the useful CSS that I use in our app.  Now we can just embed it in our layout by doing:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php include_partial('sfFacebook/css');?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The following classes are useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.app_content&lt;/code&gt; is the div surrounding the main content of the page.  It gives the canvas some padding (actually it gives itself some margin as not to butt-up against the canvas.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.box&lt;/code&gt; this defines the classic facebook box with a dark blue border at the top and with headers that have the lighter blue background and dark blue text.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.box .header&lt;/code&gt; the header for the box described above.  Use an &lt;code&gt;h2&lt;/code&gt; for the title.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.box .content&lt;/code&gt; the main content section of a box, has a bit of padding.&lt;/li&gt;
&lt;/ul&gt;


&lt;div class=&quot;footnotes&quot;&gt;
&lt;ol&gt;
&lt;li id=&quot;fbmlcss_fn_1&quot;&gt;I'm not completely convinced that the
&lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; app for facebook should be using FBML.

&lt;a href=&quot;#fbmlcss_fnr_1&quot; class=&quot;footnoteBackLink&quot;  title=&quot;Jump back to footnote  in the text.&quot;&gt;&amp;#8617;&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Facebook Markup Language: the Dashboard and Action links</title>
   <link href="http://davedash.com/2007/07/25/facebook-markup-language-the-dashboard-and-action-links/"/>
   <updated>2007-07-25T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/07/25/facebook-markup-language-the-dashboard-and-action-links</id>
   <content type="html">&lt;p&gt;[tags]facebook, fbml, apps, reviewsby.us, symfony, sfFacebookPlatformPlugin, plugins[/tags]&lt;/p&gt;

&lt;p&gt;Facebook has the concept of the dashboard:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://developers.facebook.com/images/fbml_dashboard.gif&quot; alt=&quot;dashboard&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In case the documentation isn't clear, these are where the top buttons for your app go.&lt;/p&gt;

&lt;p&gt;I created the FBMLHelper to help you write links from symfony to facebook.&lt;/p&gt;

&lt;p&gt;The dashboard itself is easy to create:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;fb:dashboard&amp;gt;
&amp;lt;/fb:dashboard&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The links are fairly simple, but if you use &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;...  we like helpers...&lt;/p&gt;

&lt;p&gt;So the &lt;code&gt;FBMLHelper&lt;/code&gt; has an &lt;code&gt;fb_action&lt;/code&gt; method which is similar to &lt;code&gt;link_to&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php echo fb_action('My Dining', '@homepage') ?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Will actually render as:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;http://apps.facebook.com/reviewsbyus/&quot;&amp;gt;My Dining&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Provided the &lt;code&gt;app_facebook_canvas_url&lt;/code&gt; is set to your canvas url (for example, our's is http://apps.facebook.com/reviewsbyus/).&lt;/p&gt;

&lt;p&gt;This helper takes care of routing and rewriting the URL to something that will work within Facebook's canvas.&lt;/p&gt;

&lt;p&gt;... so start writing some apps.  This plugin will be developed further and I'll try to publish tutorials whenever possible.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Facebook Markup Language: the Dashboard and Action links</title>
   <link href="http://davedash.com/2007/07/25/facebook-markup-language-the-dashboard-and-action-links/"/>
   <updated>2007-07-25T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/07/25/facebook-markup-language-the-dashboard-and-action-links</id>
   <content type="html">&lt;p&gt;[tags]facebook, fbml, apps, reviewsby.us, symfony, sfFacebookPlatformPlugin, plugins[/tags]&lt;/p&gt;

&lt;p&gt;Facebook has the concept of the dashboard:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://developers.facebook.com/images/fbml_dashboard.gif&quot; alt=&quot;dashboard&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In case the documentation isn't clear, these are where the top buttons for your app go.&lt;/p&gt;

&lt;p&gt;I created the FBMLHelper to help you write links from symfony to facebook.&lt;/p&gt;

&lt;p&gt;The dashboard itself is easy to create:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;fb:dashboard&amp;gt;
&amp;lt;/fb:dashboard&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The links are fairly simple, but if you use &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;...  we like helpers...&lt;/p&gt;

&lt;p&gt;So the &lt;code&gt;FBMLHelper&lt;/code&gt; has an &lt;code&gt;fb_action&lt;/code&gt; method which is similar to &lt;code&gt;link_to&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php echo fb_action('My Dining', '@homepage') ?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Will actually render as:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;http://apps.facebook.com/reviewsbyus/&quot;&amp;gt;My Dining&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Provided the &lt;code&gt;app_facebook_canvas_url&lt;/code&gt; is set to your canvas url (for example, our's is http://apps.facebook.com/reviewsbyus/).&lt;/p&gt;

&lt;p&gt;This helper takes care of routing and rewriting the URL to something that will work within Facebook's canvas.&lt;/p&gt;

&lt;p&gt;... so start writing some apps.  This plugin will be developed further and I'll try to publish tutorials whenever possible.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Dynamically adjusting your page title in symfony</title>
   <link href="http://davedash.com/2007/07/18/dynamically-adjusting-your-page-title-in-symfony/"/>
   <updated>2007-07-18T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/07/18/dynamically-adjusting-your-page-title-in-symfony</id>
   <content type="html">&lt;p&gt;[tags]view, view.yml, symfony, reviewsby.us, title, seo[/tags]&lt;/p&gt;

&lt;p&gt;A lot of the content on &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsBy.us&lt;/a&gt; and other sites we make using &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; have dynamic content.  We try to have our page titles reflect the content by prepending the name of the specific restaurant, document or menu item before the site name.&lt;/p&gt;

&lt;p&gt;To do this we use a method called &lt;code&gt;prependTitle&lt;/code&gt;.  I define this in a file called &lt;code&gt;myActions.class.php&lt;/code&gt; which almost all of my actions subclass in my projects.  This way I can enhance all the actions simply by adjusting the common ancestor, &lt;code&gt;myActions&lt;/code&gt;:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
      public function prependTitle($title)
      {
        $r = $this-&gt;getResponse();
        $d = sfConfig::get('app_title_delimiter', ' &amp;laquo; ');
        $t = sfConfig::get('app_title');
        $r-&gt;setTitle($title.$d.$t, false);
      }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The page title isn't stored anywhere, so we have to put it in &lt;code&gt;app.yml&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;app:
  title: reviewsby.us
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Voila!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Dynamically adjusting your page title in symfony</title>
   <link href="http://davedash.com/2007/07/18/dynamically-adjusting-your-page-title-in-symfony/"/>
   <updated>2007-07-18T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/07/18/dynamically-adjusting-your-page-title-in-symfony</id>
   <content type="html">&lt;p&gt;[tags]view, view.yml, symfony, reviewsby.us, title, seo[/tags]&lt;/p&gt;

&lt;p&gt;A lot of the content on &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsBy.us&lt;/a&gt; and other sites we make using &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; have dynamic content.  We try to have our page titles reflect the content by prepending the name of the specific restaurant, document or menu item before the site name.&lt;/p&gt;

&lt;p&gt;To do this we use a method called &lt;code&gt;prependTitle&lt;/code&gt;.  I define this in a file called &lt;code&gt;myActions.class.php&lt;/code&gt; which almost all of my actions subclass in my projects.  This way I can enhance all the actions simply by adjusting the common ancestor, &lt;code&gt;myActions&lt;/code&gt;:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
      public function prependTitle($title)
      {
        $r = $this-&gt;getResponse();
        $d = sfConfig::get('app_title_delimiter', ' &amp;laquo; ');
        $t = sfConfig::get('app_title');
        $r-&gt;setTitle($title.$d.$t, false);
      }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The page title isn't stored anywhere, so we have to put it in &lt;code&gt;app.yml&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;app:
  title: reviewsby.us
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Voila!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>doctine and getState()</title>
   <link href="http://davedash.com/2007/07/10/doctine-and-getstate/"/>
   <updated>2007-07-10T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/07/10/doctine-and-getstate</id>
   <content type="html">&lt;p&gt;[tags]doctrine, php, symfony, sfDoctrine, database,errors[/tags]&lt;/p&gt;

&lt;p&gt;I tend to have models with a field called &lt;code&gt;state&lt;/code&gt;.  Doctrine offers a few ways of getting to the &lt;code&gt;state&lt;/code&gt; field:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$obj-&amp;gt;get('state');
$obj['state'];
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;$obj-&amp;gt;getState()&lt;/code&gt; however conflicts with &lt;code&gt;Doctrine_Record::getState()&lt;/code&gt; from which all objects inherit.  Use one of the above alternatives.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Using sfDoctrine to match allowed email domains</title>
   <link href="http://davedash.com/2007/07/09/using-sfdoctrine-to-match-allowed-email-domains/"/>
   <updated>2007-07-09T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/07/09/using-sfdoctrine-to-match-allowed-email-domains</id>
   <content type="html">&lt;p&gt;[tags]php, propel, doctrine, validators, symfony, optiopt, startup[/tags]&lt;/p&gt;

&lt;p&gt;I'm a co-founder at an online finance web site and I'm in charge with building out the site.  Our rollout strategy is to let a a handful of companies at a time, so we're limiting registration based on your company's email address.&lt;/p&gt;

&lt;p&gt;I decided to follow the bandwagon and use PHP Doctrine.  We'll define two objects, &lt;code&gt;Company&lt;/code&gt; and &lt;code&gt;CompanyDomain&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Company:
  tableName: company
  columns:
    name: {type: string(100)}
    created_at: timestamp
    updated_at: timestamp

CompanyDomain:
  tableName: company_domain
  columns:
    company_id:
      foreignClass: Company
      cascadeDelete: true
    pattern: {type: string(100)}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Where a &lt;code&gt;CompanyDomain&lt;/code&gt; represents a domain name for a company.  E.g. Motorola might have both &lt;code&gt;motorola.com&lt;/code&gt; and &lt;code&gt;mot.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can validate a signup form and see if we've got an email address from a domain we recognize.  I like using the &lt;code&gt;validationXXX&lt;/code&gt; methods in an action class for specific validation.  I made one called &lt;code&gt;validateSignup&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public function validateSignup()
{
    if ($this-&amp;gt;isPost())
    {
        $email = $this-&amp;gt;getRequestParameter('company_email');
        if (!$company = CompanyTable::match($email))
        {
            $this-&amp;gt;getRequest()-&amp;gt;setError('company_email', 'Your work email doesn\'t appear to belong to any of the registered companies.');
            return false;
        }
        return true;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In our &lt;code&gt;CompanyTable&lt;/code&gt; we create a static function &lt;code&gt;CompanyTable::match&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;static public function match($email)
{
    $company = Doctrine_Query::create()-&amp;gt;from('Company c, c.CompanyDomains d')-&amp;gt;where('? LIKE CONCAT(\'%\', pattern)', $email)-&amp;gt;execute()-&amp;gt;getFirst();

    return $company;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note that we transparently do a join in the &lt;code&gt;-&amp;gt;from()&lt;/code&gt; statement.  If there's a match we get a company object (which we can associate with the new user) otherwise we get a &lt;code&gt;null&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;Enjoy.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Using sfDoctrine to match allowed email domains</title>
   <link href="http://davedash.com/2007/07/09/using-sfdoctrine-to-match-allowed-email-domains/"/>
   <updated>2007-07-09T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/07/09/using-sfdoctrine-to-match-allowed-email-domains</id>
   <content type="html">&lt;p&gt;[tags]php, propel, doctrine, validators, symfony, optiopt, startup[/tags]&lt;/p&gt;

&lt;p&gt;I'm a co-founder at an online finance web site and I'm in charge with building out the site.  Our rollout strategy is to let a a handful of companies at a time, so we're limiting registration based on your company's email address.&lt;/p&gt;

&lt;p&gt;I decided to follow the bandwagon and use PHP Doctrine.  We'll define two objects, &lt;code&gt;Company&lt;/code&gt; and &lt;code&gt;CompanyDomain&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Company:
  tableName: company
  columns:
    name: {type: string(100)}
    created_at: timestamp
    updated_at: timestamp

CompanyDomain:
  tableName: company_domain
  columns:
    company_id:
      foreignClass: Company
      cascadeDelete: true
    pattern: {type: string(100)}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Where a &lt;code&gt;CompanyDomain&lt;/code&gt; represents a domain name for a company.  E.g. Motorola might have both &lt;code&gt;motorola.com&lt;/code&gt; and &lt;code&gt;mot.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can validate a signup form and see if we've got an email address from a domain we recognize.  I like using the &lt;code&gt;validationXXX&lt;/code&gt; methods in an action class for specific validation.  I made one called &lt;code&gt;validateSignup&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public function validateSignup()
{
    if ($this-&amp;gt;isPost())
    {
        $email = $this-&amp;gt;getRequestParameter('company_email');
        if (!$company = CompanyTable::match($email))
        {
            $this-&amp;gt;getRequest()-&amp;gt;setError('company_email', 'Your work email doesn\'t appear to belong to any of the registered companies.');
            return false;
        }
        return true;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In our &lt;code&gt;CompanyTable&lt;/code&gt; we create a static function &lt;code&gt;CompanyTable::match&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;static public function match($email)
{
    $company = Doctrine_Query::create()-&amp;gt;from('Company c, c.CompanyDomains d')-&amp;gt;where('? LIKE CONCAT(\'%\', pattern)', $email)-&amp;gt;execute()-&amp;gt;getFirst();

    return $company;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note that we transparently do a join in the &lt;code&gt;-&amp;gt;from()&lt;/code&gt; statement.  If there's a match we get a company object (which we can associate with the new user) otherwise we get a &lt;code&gt;null&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;Enjoy.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Saving a file to a database using symfony and doctrine</title>
   <link href="http://davedash.com/2007/07/07/saving-a-file-to-a-database-using-symfony-and-doctrine/"/>
   <updated>2007-07-07T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/07/07/saving-a-file-to-a-database-using-symfony-and-doctrine</id>
   <content type="html">&lt;p&gt;[tags]symfony, doctrine, database, uploads, wordpress, fixme[/tags]&lt;/p&gt;

&lt;p&gt;I like to save content uploaded by website visitors to a database versus the file system.  It makes it easy having the data all in one spot.&lt;/p&gt;

&lt;p&gt;I tend to overcomplicate this process, so I wanted to write down the important steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;In the template set &lt;code&gt;multipart=true&lt;/code&gt; for the form.  If you're using symfony helpers you can do this via:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; form_tag('@action', 'multipart=true')
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extract the binary data you want to store in the database in your action:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; $path = $this-&amp;gt;getRequest()-&amp;gt;getFilePath('my_file');
 $size = $this-&amp;gt;getRequest()-&amp;gt;getFileSize('my_file');
 $type = $this-&amp;gt;getRequest()-&amp;gt;getFileType('my_file');    
 $data = fread(f_open($path, &quot;r&quot;), $size);

 $myObject              = new MyObject();
 $myObject['file_data'] = $data;
 $myObject['file_size'] = $size;
 $myObject['mime_type'] = $type;
 $myObject-&amp;gt;save();
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Wordpress is garbage so f_open should not have an underscore... what to do...&lt;/p&gt;

&lt;p&gt;At least that's how we role with Doctrine.  Note, Doctrine syntax is changing, and this may not be the most efficient way to create a new &lt;code&gt;Doctrine Record&lt;/code&gt; in your application.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Saving a file to a database using symfony and doctrine</title>
   <link href="http://davedash.com/2007/07/07/saving-a-file-to-a-database-using-symfony-and-doctrine/"/>
   <updated>2007-07-07T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/07/07/saving-a-file-to-a-database-using-symfony-and-doctrine</id>
   <content type="html">&lt;p&gt;[tags]symfony, doctrine, database, uploads, wordpress, fixme[/tags]&lt;/p&gt;

&lt;p&gt;I like to save content uploaded by website visitors to a database versus the file system.  It makes it easy having the data all in one spot.&lt;/p&gt;

&lt;p&gt;I tend to overcomplicate this process, so I wanted to write down the important steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;In the template set &lt;code&gt;multipart=true&lt;/code&gt; for the form.  If you're using symfony helpers you can do this via:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; form_tag('@action', 'multipart=true')
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extract the binary data you want to store in the database in your action:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; $path = $this-&amp;gt;getRequest()-&amp;gt;getFilePath('my_file');
 $size = $this-&amp;gt;getRequest()-&amp;gt;getFileSize('my_file');
 $type = $this-&amp;gt;getRequest()-&amp;gt;getFileType('my_file');    
 $data = fread(f_open($path, &quot;r&quot;), $size);

 $myObject              = new MyObject();
 $myObject['file_data'] = $data;
 $myObject['file_size'] = $size;
 $myObject['mime_type'] = $type;
 $myObject-&amp;gt;save();
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Wordpress is garbage so f_open should not have an underscore... what to do...&lt;/p&gt;

&lt;p&gt;At least that's how we role with Doctrine.  Note, Doctrine syntax is changing, and this may not be the most efficient way to create a new &lt;code&gt;Doctrine Record&lt;/code&gt; in your application.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Firebug Lite and symfony</title>
   <link href="http://davedash.com/2007/06/02/firebug-lite-and-symfony/"/>
   <updated>2007-06-02T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/06/02/firebug-lite-and-symfony</id>
   <content type="html">&lt;p&gt;I wrote a
&lt;a href=&quot;http://trac.symfony-project.com/trac/wiki/sfFirebugLitePlugin&quot;&gt;symfony plugin to enable the Firebug Lite javascript&lt;/a&gt;
in your &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; app (in much the same way that &lt;code&gt;web_debug&lt;/code&gt; works).&lt;/p&gt;

&lt;p&gt;Firebug, Gmail Manager and Adblock are all plugins I use regularly in Firefox.
However, I've been getting tired of Firefox's hunger for my resources, so I
want to be as browser independent as possible.  So this plugin allows me (and
you) to embed some of the debugging functionality of Firebug in other browsers.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Firebug Lite and symfony</title>
   <link href="http://davedash.com/2007/06/02/firebug-lite-and-symfony/"/>
   <updated>2007-06-02T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/06/02/firebug-lite-and-symfony</id>
   <content type="html">&lt;p&gt;I wrote a
&lt;a href=&quot;http://trac.symfony-project.com/trac/wiki/sfFirebugLitePlugin&quot;&gt;symfony plugin to enable the Firebug Lite javascript&lt;/a&gt;
in your &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; app (in much the same way that &lt;code&gt;web_debug&lt;/code&gt; works).&lt;/p&gt;

&lt;p&gt;Firebug, Gmail Manager and Adblock are all plugins I use regularly in Firefox.
However, I've been getting tired of Firefox's hunger for my resources, so I
want to be as browser independent as possible.  So this plugin allows me (and
you) to embed some of the debugging functionality of Firebug in other browsers.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Boosting terms in  Zend Search Lucene</title>
   <link href="http://davedash.com/2007/05/29/boosting-terms-in-zend-search-lucene/"/>
   <updated>2007-05-29T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/05/29/boosting-terms-in-zend-search-lucene</id>
   <content type="html">&lt;p&gt;[tags]Zend, Zend Search Lucene, Search, Lucene, php, symfony, zsl[/tags]&lt;/p&gt;

&lt;h3&gt;Boosting terms &amp;mdash; some fields are better than others&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;Lucene&lt;/a&gt; supports boosting or weighting terms.  For example, if I search for members of a web site, and I type in &lt;q&gt;Dash&lt;/q&gt;, I want people with the name &lt;q&gt;Dash&lt;/q&gt; to take precendence over somebody who has a hobby of running the 50-yard Dash.&lt;/p&gt;

&lt;p&gt;If we look at our &lt;code&gt;generateZSLDocument()&lt;/code&gt; method we defined we just need to adjust a few lines:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;

        $doc-&gt;addField(Zend_Search_Lucene_Field::Text('firstname', $this-&gt;getFirstname()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Text('lastname', $this-&gt;getLastname()));
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Should be turned into:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;

        $field = Zend_Search_Lucene_Field::Text('firstname', $this-&gt;getFirstname());
        $field-&gt;boost = 1.5;
        $doc-&gt;addField($field);
        $field = Zend_Search_Lucene_Field::Text('lastname', $this-&gt;getLastname());
        $field-&gt;boost = 1.5;
        $doc-&gt;addField($field);

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;This is pretty straight forward way to add weight (1.5 times the weight of a normal term) and you can customize it to the needs of your site.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Boosting terms in  Zend Search Lucene</title>
   <link href="http://davedash.com/2007/05/29/boosting-terms-in-zend-search-lucene/"/>
   <updated>2007-05-29T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/05/29/boosting-terms-in-zend-search-lucene</id>
   <content type="html">&lt;p&gt;[tags]Zend, Zend Search Lucene, Search, Lucene, php, symfony, zsl[/tags]&lt;/p&gt;

&lt;h3&gt;Boosting terms &amp;mdash; some fields are better than others&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;Lucene&lt;/a&gt; supports boosting or weighting terms.  For example, if I search for members of a web site, and I type in &lt;q&gt;Dash&lt;/q&gt;, I want people with the name &lt;q&gt;Dash&lt;/q&gt; to take precendence over somebody who has a hobby of running the 50-yard Dash.&lt;/p&gt;

&lt;p&gt;If we look at our &lt;code&gt;generateZSLDocument()&lt;/code&gt; method we defined we just need to adjust a few lines:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;

        $doc-&gt;addField(Zend_Search_Lucene_Field::Text('firstname', $this-&gt;getFirstname()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Text('lastname', $this-&gt;getLastname()));
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Should be turned into:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;

        $field = Zend_Search_Lucene_Field::Text('firstname', $this-&gt;getFirstname());
        $field-&gt;boost = 1.5;
        $doc-&gt;addField($field);
        $field = Zend_Search_Lucene_Field::Text('lastname', $this-&gt;getLastname());
        $field-&gt;boost = 1.5;
        $doc-&gt;addField($field);

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;This is pretty straight forward way to add weight (1.5 times the weight of a normal term) and you can customize it to the needs of your site.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Finding things using Zend Search Lucene in symfony</title>
   <link href="http://davedash.com/2007/05/23/finding-things-using-zend-search-lucene-in-symfony/"/>
   <updated>2007-05-23T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/05/23/finding-things-using-zend-search-lucene-in-symfony</id>
   <content type="html">&lt;p&gt;[tags]Zend, Zend Search Lucene, Search, Lucene, php, symfony, zsl[/tags]&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;notice&quot;&gt;This is part of an &lt;a href=&quot;http://spindrop.us/tag/zsl&quot;&gt;on going series&lt;/a&gt; about the Zend Search Lucene libraries and symfony.  We'll pretty everything up when we're done =)&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;We now know how to &lt;a href=&quot;http://spindrop.us/2007/04/24/creating-updating-deleting-documents-in-a-lucene-index-with-symfony/&quot;&gt;manipulate the index via our model classes&lt;/a&gt;.  But let's actually do something useful with our search engine... let's search!&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;[tags]Zend, Zend Search Lucene, Search, Lucene, php, symfony, zsl[/tags]&lt;/p&gt;

&lt;p&gt;At the time of this writing we're dealing with Propel which uses &lt;code&gt;Peer&lt;/code&gt; classes which are meant for dealing with multiple objects&lt;sup id=&quot;#fnr_1&quot;&gt;&lt;a href=&quot;#fn_1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.  This is the perfect place for a &lt;code&gt;::search()&lt;/code&gt; method.  In otherwords, &lt;code&gt;UserPeer::search('dave');&lt;/code&gt; should query Lucene for users matching &quot;dave&quot;.  Let's make that happen:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;

    public static function search($query)
    {
        $index = self::getLuceneIndex();
        
        $hits = $index-&gt;find(strtolower($query));
        $pks = array();
    
        foreach($hits AS $hit)
        {
            $pks[] = $hit-&gt;user_id;
        }
        
        return self::retrieveByPks($pks);
    }

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;What we're doing is retrieving our Lucene index.  Somewhere between tutorials we wrote this &lt;code&gt;Peer&lt;/code&gt; function to handle that:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public static function getLuceneIndex($autoIndex = true)
    {
        try 
        {
            return $index = Zend_Search_Lucene::open(sfConfig::get(self::$luceneIndex));
        } 
        catch (Exception $e) 
        {
            $index = $autoIndex ? self::reindex() : null;
            return $index;
        }
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;If our index is missing we'll conveniently create it on the fly.  We then use the Zend Search Lucene API to retrieve the matching hits in this index and then use some Propel trickery to retrieve by an array of primary keys.&lt;/p&gt;

&lt;p&gt;It's now simple to use &lt;code&gt;::search()&lt;/code&gt; functions in the same manner as you use &lt;code&gt;::doSelect()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At this point you should be able to create a basic symfony app that can utilize a Lucene index.&lt;/p&gt;

&lt;div id=&quot;footnotes&quot;&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
        &lt;li id=&quot;fn_1&quot;&gt;The examples refer to using Propel, but it's trivial to adapt this to sfDoctrine &lt;a href=&quot;#fnr_1&quot; class=&quot;footnoteBackLink&quot;  title=&quot;Jump back to footnote  in the text.&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Finding things using Zend Search Lucene in symfony</title>
   <link href="http://davedash.com/2007/05/23/finding-things-using-zend-search-lucene-in-symfony/"/>
   <updated>2007-05-23T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/05/23/finding-things-using-zend-search-lucene-in-symfony</id>
   <content type="html">&lt;p&gt;[tags]Zend, Zend Search Lucene, Search, Lucene, php, symfony, zsl[/tags]&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;notice&quot;&gt;This is part of an &lt;a href=&quot;http://spindrop.us/tag/zsl&quot;&gt;on going series&lt;/a&gt; about the Zend Search Lucene libraries and symfony.  We'll pretty everything up when we're done =)&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;We now know how to &lt;a href=&quot;http://spindrop.us/2007/04/24/creating-updating-deleting-documents-in-a-lucene-index-with-symfony/&quot;&gt;manipulate the index via our model classes&lt;/a&gt;.  But let's actually do something useful with our search engine... let's search!&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;[tags]Zend, Zend Search Lucene, Search, Lucene, php, symfony, zsl[/tags]&lt;/p&gt;

&lt;p&gt;At the time of this writing we're dealing with Propel which uses &lt;code&gt;Peer&lt;/code&gt; classes which are meant for dealing with multiple objects&lt;sup id=&quot;#fnr_1&quot;&gt;&lt;a href=&quot;#fn_1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.  This is the perfect place for a &lt;code&gt;::search()&lt;/code&gt; method.  In otherwords, &lt;code&gt;UserPeer::search('dave');&lt;/code&gt; should query Lucene for users matching &quot;dave&quot;.  Let's make that happen:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;

    public static function search($query)
    {
        $index = self::getLuceneIndex();
        
        $hits = $index-&gt;find(strtolower($query));
        $pks = array();
    
        foreach($hits AS $hit)
        {
            $pks[] = $hit-&gt;user_id;
        }
        
        return self::retrieveByPks($pks);
    }

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;What we're doing is retrieving our Lucene index.  Somewhere between tutorials we wrote this &lt;code&gt;Peer&lt;/code&gt; function to handle that:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public static function getLuceneIndex($autoIndex = true)
    {
        try 
        {
            return $index = Zend_Search_Lucene::open(sfConfig::get(self::$luceneIndex));
        } 
        catch (Exception $e) 
        {
            $index = $autoIndex ? self::reindex() : null;
            return $index;
        }
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;If our index is missing we'll conveniently create it on the fly.  We then use the Zend Search Lucene API to retrieve the matching hits in this index and then use some Propel trickery to retrieve by an array of primary keys.&lt;/p&gt;

&lt;p&gt;It's now simple to use &lt;code&gt;::search()&lt;/code&gt; functions in the same manner as you use &lt;code&gt;::doSelect()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At this point you should be able to create a basic symfony app that can utilize a Lucene index.&lt;/p&gt;

&lt;div id=&quot;footnotes&quot;&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
        &lt;li id=&quot;fn_1&quot;&gt;The examples refer to using Propel, but it's trivial to adapt this to sfDoctrine &lt;a href=&quot;#fnr_1&quot; class=&quot;footnoteBackLink&quot;  title=&quot;Jump back to footnote  in the text.&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Debugging yaml configuration with the symfony web debugger</title>
   <link href="http://davedash.com/2007/05/21/debugging-yaml-configuration-with-the-symfony-web-debugger/"/>
   <updated>2007-05-21T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/05/21/debugging-yaml-configuration-with-the-symfony-web-debugger</id>
   <content type="html">&lt;p&gt;[tags]symfony, yaml, configuration, web debug, debug[/tags]
There's no doubt that &lt;a href=&quot;http://www.yaml.org/&quot;&gt;yaml&lt;/a&gt; configuration files in &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; can be troublesome to debug.&lt;/p&gt;

&lt;p&gt;The symfony web debugger comes to the rescue.&lt;/p&gt;

&lt;p&gt;Let's say you're &lt;code&gt;app.yml&lt;/code&gt; has the following:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;app:
  admin:
    email: me@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In your development environment you can look at click on &quot;vars &amp;amp; config&quot;  and under settings see all the &lt;code&gt;sfConfig::get&lt;/code&gt;able variables.  You'll find the above listed as:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;app_admin_email: me@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This technique is very handy when trying to figure out what key you need to use in &lt;code&gt;sfConfig::get()&lt;/code&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Debugging yaml configuration with the symfony web debugger</title>
   <link href="http://davedash.com/2007/05/21/debugging-yaml-configuration-with-the-symfony-web-debugger/"/>
   <updated>2007-05-21T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/05/21/debugging-yaml-configuration-with-the-symfony-web-debugger</id>
   <content type="html">&lt;p&gt;[tags]symfony, yaml, configuration, web debug, debug[/tags]
There's no doubt that &lt;a href=&quot;http://www.yaml.org/&quot;&gt;yaml&lt;/a&gt; configuration files in &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; can be troublesome to debug.&lt;/p&gt;

&lt;p&gt;The symfony web debugger comes to the rescue.&lt;/p&gt;

&lt;p&gt;Let's say you're &lt;code&gt;app.yml&lt;/code&gt; has the following:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;app:
  admin:
    email: me@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In your development environment you can look at click on &quot;vars &amp;amp; config&quot;  and under settings see all the &lt;code&gt;sfConfig::get&lt;/code&gt;able variables.  You'll find the above listed as:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;app_admin_email: me@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This technique is very handy when trying to figure out what key you need to use in &lt;code&gt;sfConfig::get()&lt;/code&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Adventures in stupidity... strip_comments and SF_DEBUG</title>
   <link href="http://davedash.com/2007/05/07/adventures-in-stupidity-strip_comments-and-sf_debug/"/>
   <updated>2007-05-07T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/05/07/adventures-in-stupidity-strip_comments-and-sf_debug</id>
   <content type="html">&lt;p&gt;[tags]symfony, media temple, fast cgi, anomalies, SNAFU, php, configuration[tags]&lt;/p&gt;

&lt;p&gt;Often I run into &lt;acronym title=&quot;Situation Normal, All Fucked Up&quot;&gt;SNAFU&lt;/acronym&gt;s... generally these are minimized by clever frameworks, design patterns, normalizers, etc.  Lately I've been working with a &lt;a href=&quot;http://mirthlab.com/&quot;&gt;Mark Quezada&lt;/a&gt; on a client hosted at &lt;a href=&quot;http://mediatemple.com/&quot;&gt;Media Temple&lt;/a&gt; on a dedicated virtual server.&lt;/p&gt;

&lt;p&gt;Unfortunately some combination of FastCGI, their build of PHP and symfony just wasn't working.  I beat my head for a few hours and we found out that setting &lt;code&gt;SF_DEBUG&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; would get rid of the problem, or more specifically setting &lt;code&gt;strip_comments&lt;/code&gt; to &lt;code&gt;off&lt;/code&gt; would fix this.&lt;/p&gt;

&lt;p&gt;Without getting my hands too dirty, something in the way that &lt;code&gt;sfToolkit::stripComments&lt;/code&gt; strips comments (and I don't know when this happens, but I imagine it happens for compiling and caching parts of the code) doesn't jive well with our servers configuration.&lt;/p&gt;

&lt;p&gt;Needless to say, I very much prefer having full control on as many layers of &quot;LAMP&quot; as possible to ensure that what I want done is getting done, even if it means compiling Apache and PHP by hand.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Creating, Updating, Deleting documents in a Lucene Index with symfony</title>
   <link href="http://davedash.com/2007/04/24/creating-updating-deleting-documents-in-a-lucene-index-with-symfony/"/>
   <updated>2007-04-24T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/24/creating-updating-deleting-documents-in-a-lucene-index-with-symfony</id>
   <content type="html">&lt;p&gt;Previously we covered &lt;a href=&quot;http://spindrop.us/2007/04/23/the-lucene-search-index-and-symfony/&quot;&gt;an all-at-once approach&lt;/a&gt; to indexing objects in your symfony app.  But for some reason, people find the need to allow users to sign up, or change their email addresses and then all of a sudden our wonderful Lucene index is out of date.&lt;/p&gt;

&lt;p&gt;Here lies the strength of using &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;Zend Search Lucene&lt;/a&gt; in your app, you can now get the flexibility of interacting with a Lucene index, no matter how it was created and add, update and delete documents to it.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;The last thing you want to do is have a cron job in charge of making sure your index is always up to date by reindexing regularly.  This is an inelegant and inefficient process.&lt;/p&gt;

&lt;p&gt;A smarter method would be to trigger an update of the index each time you update your database.  Luckily the &lt;acronym title=&quot;Object Relational Mapping&quot;&gt;ORM&lt;/acronym&gt; layer allows us to do this using objects (in our case Propel objects).&lt;/p&gt;

&lt;p&gt;If we look at our &lt;a href=&quot;http://spindrop.us/2007/04/23/the-lucene-search-index-and-symfony/&quot;&gt;user example from before&lt;/a&gt;, we did set ourselves up to easily do this using our &lt;code&gt;User::generateZSLDocument()&lt;/code&gt; function, which did most of the heavy lifting.&lt;/p&gt;

&lt;p&gt;We can make a few small changes to the &lt;code&gt;User&lt;/code&gt; class:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    var $reindex = false;
    public function setUsername ( $v )
    {
        parent::setUsername($v);
        $this-&gt;reindex = true;
    }
    public function setFirstname ( $v )
    {
        parent::setFirstname($v);
        $this-&gt;reindex = true;
    }
    public function setLastname ( $v )
    {
        parent::setLastname($v);
        $this-&gt;reindex = true;
    }
    public function setEmail ( $v )
    {
        parent::setEmail($v);
        $this-&gt;reindex = true;
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;We have an attribute called &lt;code&gt;$reindex&lt;/code&gt;.  When it is false we don't need to worry about the index.  When something significant changes, like an update to your name or email address, then we set &lt;code&gt;$reindex&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;.  Then when we save with an overridden save method:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function save ($con = null)
    {
        parent::save($con);
      
        if ($this-&gt;reindex) 
        {
            $index = $this-&gt;removeFromIndex();
            $doc   = $this-&gt;generateZSLDocument();
            $index-&gt;addDocument($doc);
        }
    }

    public function removeFromIndex() 
    {
        $index = Zend_Search_Lucene::open(sfConfig::get('app_search_user_index'));  

        // remove old documents
        $term  = new Zend_Search_Lucene_Index_Term($this-&gt;getId(), 'userid');
        $query = new Zend_Search_Lucene_Search_Query_Term($term);
        $hits  = array();
        $hits  = $index-&gt;find($query);

        foreach ($hits AS $hit) 
        {  
            $index-&gt;delete($hit-&gt;id);  
        }

        return $index;      
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Now we've got the &lt;em&gt;exact&lt;/em&gt; same data that we created during &lt;a href=&quot;http://spindrop.us/2007/04/23/the-lucene-search-index-and-symfony/&quot;&gt;our original indexing&lt;/a&gt;.  This handled creating and updating object, but we miss updating the index when deleting objects.&lt;/p&gt;

&lt;p&gt;Luckily we already made a function &lt;code&gt;User::removeFromIndex()&lt;/code&gt; to remove any related documents from the index, so our delete function can be pretty simple:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function delete($con = null)
    {
        parent::delete($con);
        $this-&gt;removeFromIndex();
    }
&lt;/textarea&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Creating, Updating, Deleting documents in a Lucene Index with symfony</title>
   <link href="http://davedash.com/2007/04/24/creating-updating-deleting-documents-in-a-lucene-index-with-symfony/"/>
   <updated>2007-04-24T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/24/creating-updating-deleting-documents-in-a-lucene-index-with-symfony</id>
   <content type="html">&lt;p&gt;Previously we covered &lt;a href=&quot;http://spindrop.us/2007/04/23/the-lucene-search-index-and-symfony/&quot;&gt;an all-at-once approach&lt;/a&gt; to indexing objects in your symfony app.  But for some reason, people find the need to allow users to sign up, or change their email addresses and then all of a sudden our wonderful Lucene index is out of date.&lt;/p&gt;

&lt;p&gt;Here lies the strength of using &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;Zend Search Lucene&lt;/a&gt; in your app, you can now get the flexibility of interacting with a Lucene index, no matter how it was created and add, update and delete documents to it.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;The last thing you want to do is have a cron job in charge of making sure your index is always up to date by reindexing regularly.  This is an inelegant and inefficient process.&lt;/p&gt;

&lt;p&gt;A smarter method would be to trigger an update of the index each time you update your database.  Luckily the &lt;acronym title=&quot;Object Relational Mapping&quot;&gt;ORM&lt;/acronym&gt; layer allows us to do this using objects (in our case Propel objects).&lt;/p&gt;

&lt;p&gt;If we look at our &lt;a href=&quot;http://spindrop.us/2007/04/23/the-lucene-search-index-and-symfony/&quot;&gt;user example from before&lt;/a&gt;, we did set ourselves up to easily do this using our &lt;code&gt;User::generateZSLDocument()&lt;/code&gt; function, which did most of the heavy lifting.&lt;/p&gt;

&lt;p&gt;We can make a few small changes to the &lt;code&gt;User&lt;/code&gt; class:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    var $reindex = false;
    public function setUsername ( $v )
    {
        parent::setUsername($v);
        $this-&gt;reindex = true;
    }
    public function setFirstname ( $v )
    {
        parent::setFirstname($v);
        $this-&gt;reindex = true;
    }
    public function setLastname ( $v )
    {
        parent::setLastname($v);
        $this-&gt;reindex = true;
    }
    public function setEmail ( $v )
    {
        parent::setEmail($v);
        $this-&gt;reindex = true;
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;We have an attribute called &lt;code&gt;$reindex&lt;/code&gt;.  When it is false we don't need to worry about the index.  When something significant changes, like an update to your name or email address, then we set &lt;code&gt;$reindex&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;.  Then when we save with an overridden save method:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function save ($con = null)
    {
        parent::save($con);
      
        if ($this-&gt;reindex) 
        {
            $index = $this-&gt;removeFromIndex();
            $doc   = $this-&gt;generateZSLDocument();
            $index-&gt;addDocument($doc);
        }
    }

    public function removeFromIndex() 
    {
        $index = Zend_Search_Lucene::open(sfConfig::get('app_search_user_index'));  

        // remove old documents
        $term  = new Zend_Search_Lucene_Index_Term($this-&gt;getId(), 'userid');
        $query = new Zend_Search_Lucene_Search_Query_Term($term);
        $hits  = array();
        $hits  = $index-&gt;find($query);

        foreach ($hits AS $hit) 
        {  
            $index-&gt;delete($hit-&gt;id);  
        }

        return $index;      
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Now we've got the &lt;em&gt;exact&lt;/em&gt; same data that we created during &lt;a href=&quot;http://spindrop.us/2007/04/23/the-lucene-search-index-and-symfony/&quot;&gt;our original indexing&lt;/a&gt;.  This handled creating and updating object, but we miss updating the index when deleting objects.&lt;/p&gt;

&lt;p&gt;Luckily we already made a function &lt;code&gt;User::removeFromIndex()&lt;/code&gt; to remove any related documents from the index, so our delete function can be pretty simple:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function delete($con = null)
    {
        parent::delete($con);
        $this-&gt;removeFromIndex();
    }
&lt;/textarea&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>The Lucene Search Index and symfony</title>
   <link href="http://davedash.com/2007/04/23/the-lucene-search-index-and-symfony/"/>
   <updated>2007-04-23T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/23/the-lucene-search-index-and-symfony</id>
   <content type="html">&lt;p&gt;[tags]Zend, Zend Search Lucene, Search, Lucene, php, symfony, zsl, index[/tags]&lt;/p&gt;

&lt;p&gt;This article is meant to followup &lt;a href=&quot;http://spindrop.us/2007/04/10/sfzendplugin/&quot;&gt;sfZendPlugin&lt;/a&gt; where we learn a newer way of obtaining the &lt;a href=&quot;http://framework.zend.com/&quot;&gt;Zend Framework&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this tutorial we're going to delve into the Lucene index.  &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;Zend Search Lucene&lt;/a&gt; relies on building a Lucene index.  This is a directory that contains files that can be indexed and queried by Lucene or other ports.  In our example we'll be creating a search for user profiles.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;We'll want to store in our &lt;code&gt;app.yml&lt;/code&gt; the precise location of this index file so we can refer to it in our app&lt;sup id=&quot;#fnr_lucene_index1&quot;&gt;&lt;a href=&quot;#fn_lucene_index1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;all:
  search:
    user_index: /tmp/myapp.user.lucene.index
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now when we need to refer to the index we can do &lt;code&gt;sfConfig::get('app_search_user_index')&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Index Something&lt;/h3&gt;

&lt;p&gt;Let's try a user search where we can find a user by their name or email address.  It's fairly simple to accomplish, and hardly requires the use of &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;&lt;acronym title=&quot;Zend Search Lucene&quot;&gt;ZSL&lt;/acronym&gt;&lt;/a&gt;, but by using &lt;acronym title=&quot;Zend Search Lucene&quot;&gt;ZSL&lt;/acronym&gt; we can easily extend it to do a full-text search of a user's profile or any other textual data.&lt;/p&gt;

&lt;p&gt;Each &quot;thing&quot; stored in the index is a Lucene &quot;document&quot;.  Each document then consists of several &quot;fields&quot; (&lt;code&gt;Zend_Search_Lucene_Field&lt;/code&gt; objects).  In our example, each document will be an individual user and the fields will be relevant attributes of the user (username, first name, last name, email, the text of their profile).&lt;/p&gt;

&lt;p&gt;Initially we'll want to populate our index.  We may also want to regularly reindex all the users at once to optimize the search performance.  Since reindexing involves multiple users it would make sense to have a static &lt;code&gt;reindex&lt;/code&gt; method in our &lt;code&gt;UserPeer&lt;/code&gt; class&lt;sup id=&quot;#fnr_lucene_index2&quot;&gt;&lt;a href=&quot;#fn_fn_lucene_index2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
class UserPeer extends BaseUserPeer
{
    public static function reindex()
    {
        $index = Zend_Search_Lucene::create(sfConfig::get('app_search_user_index'));

        $user = UserPeer::doSelect(new Criteria());
        foreach ($users AS $user)
        {
            $index-&gt;addDocument($user-&gt;generateZSLDocument());
        }

        return $index;
    }
}
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Very simply, we're creating a new index, getting all the users, adding a document to the index and then committing the index (to disk).  You might have noticed that there's a strange function, &lt;code&gt;User::generateZSLDocument()&lt;/code&gt;.  This function contains all the magic.  In order to not repeat ourselves we keep the internals of making a document for the Lucene index in the &lt;code&gt;User&lt;/code&gt; class itself.  Let's look at it:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function generateZSLDocument()
    {
        $doc = new Zend_Search_Lucene_Document();
        $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('uid', $this-&gt;getId()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('username', $this-&gt;getUsername()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('email', $this-&gt;getEmail()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Text('firstname', $this-&gt;getFirstname()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Text('lastname', $this-&gt;getLastname()));
        /* An unstored contents field as an aggregate 
          * of all data is no longer needed in *ZEND* Lucene 
          * But it's here.
          */
        $doc-&gt;addField(Zend_Search_Lucene_Field::Unstored('contents', implode(' ', array($this-&gt;getEmail(), $this-&gt;getFirstname(), $this-&gt;getLastname(), $this-&gt;getUsername())));
        return $doc;
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;We're really just dumping the relevant search terms into this document.  The beauty of keeping this code internalized in the &lt;code&gt;User&lt;/code&gt; class is we can reuse it later if we need to index a single &lt;code&gt;User&lt;/code&gt; at a time.&lt;/p&gt;

&lt;p&gt;A couple things to note.  &lt;code&gt;Zend_Search_Lucene_Field::Keyword&lt;/code&gt; allows us to store data that we can lookup later.  We store the &lt;code&gt;User::id&lt;/code&gt; in a field called &lt;code&gt;uid&lt;/code&gt; since &lt;code&gt;id&lt;/code&gt; is a reserved word for the index and we can't access it from &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;Zend Search Lucene&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In a batch script or a reindex action we can now just call &lt;code&gt;UserPeer::reindex()&lt;/code&gt; and have a working search index for our users.&lt;/p&gt;

&lt;div id=&quot;footnotes&quot;&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
        &lt;li id=&quot;fn_lucene_index1&quot;&gt;Storing things in &lt;code&gt;app.yml&lt;/code&gt; is great for indexes that don't need to be searched in multiple applications. &lt;a href=&quot;#fnr_lucene_index1&quot; class=&quot;footnoteBackLink&quot;  title=&quot;Jump back to footnote 1 in the text.&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/li&gt;
        &lt;li id=&quot;fn_lucene_index2&quot;&gt;
Since we're using a Lucene index, which has an open documented structure, we aren't limited to just using Zend Search Lucene or Apache Lucene (java).  We can mix and match and read and write to the same index file.  For very large indexes (65,000+ documents), I rewrote a Java application to index all the documents at once as PHP would time out during such a task.
&lt;a href=&quot;#fnr_lucene_index2&quot; class=&quot;footnoteBackLink&quot;  title=&quot;Jump back to footnote 2 in the text.&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>The Lucene Search Index and symfony</title>
   <link href="http://davedash.com/2007/04/23/the-lucene-search-index-and-symfony/"/>
   <updated>2007-04-23T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/23/the-lucene-search-index-and-symfony</id>
   <content type="html">&lt;p&gt;[tags]Zend, Zend Search Lucene, Search, Lucene, php, symfony, zsl, index[/tags]&lt;/p&gt;

&lt;p&gt;This article is meant to followup &lt;a href=&quot;http://spindrop.us/2007/04/10/sfzendplugin/&quot;&gt;sfZendPlugin&lt;/a&gt; where we learn a newer way of obtaining the &lt;a href=&quot;http://framework.zend.com/&quot;&gt;Zend Framework&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this tutorial we're going to delve into the Lucene index.  &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;Zend Search Lucene&lt;/a&gt; relies on building a Lucene index.  This is a directory that contains files that can be indexed and queried by Lucene or other ports.  In our example we'll be creating a search for user profiles.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;We'll want to store in our &lt;code&gt;app.yml&lt;/code&gt; the precise location of this index file so we can refer to it in our app&lt;sup id=&quot;#fnr_lucene_index1&quot;&gt;&lt;a href=&quot;#fn_lucene_index1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;all:
  search:
    user_index: /tmp/myapp.user.lucene.index
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now when we need to refer to the index we can do &lt;code&gt;sfConfig::get('app_search_user_index')&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Index Something&lt;/h3&gt;

&lt;p&gt;Let's try a user search where we can find a user by their name or email address.  It's fairly simple to accomplish, and hardly requires the use of &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;&lt;acronym title=&quot;Zend Search Lucene&quot;&gt;ZSL&lt;/acronym&gt;&lt;/a&gt;, but by using &lt;acronym title=&quot;Zend Search Lucene&quot;&gt;ZSL&lt;/acronym&gt; we can easily extend it to do a full-text search of a user's profile or any other textual data.&lt;/p&gt;

&lt;p&gt;Each &quot;thing&quot; stored in the index is a Lucene &quot;document&quot;.  Each document then consists of several &quot;fields&quot; (&lt;code&gt;Zend_Search_Lucene_Field&lt;/code&gt; objects).  In our example, each document will be an individual user and the fields will be relevant attributes of the user (username, first name, last name, email, the text of their profile).&lt;/p&gt;

&lt;p&gt;Initially we'll want to populate our index.  We may also want to regularly reindex all the users at once to optimize the search performance.  Since reindexing involves multiple users it would make sense to have a static &lt;code&gt;reindex&lt;/code&gt; method in our &lt;code&gt;UserPeer&lt;/code&gt; class&lt;sup id=&quot;#fnr_lucene_index2&quot;&gt;&lt;a href=&quot;#fn_fn_lucene_index2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
class UserPeer extends BaseUserPeer
{
    public static function reindex()
    {
        $index = Zend_Search_Lucene::create(sfConfig::get('app_search_user_index'));

        $user = UserPeer::doSelect(new Criteria());
        foreach ($users AS $user)
        {
            $index-&gt;addDocument($user-&gt;generateZSLDocument());
        }

        return $index;
    }
}
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Very simply, we're creating a new index, getting all the users, adding a document to the index and then committing the index (to disk).  You might have noticed that there's a strange function, &lt;code&gt;User::generateZSLDocument()&lt;/code&gt;.  This function contains all the magic.  In order to not repeat ourselves we keep the internals of making a document for the Lucene index in the &lt;code&gt;User&lt;/code&gt; class itself.  Let's look at it:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function generateZSLDocument()
    {
        $doc = new Zend_Search_Lucene_Document();
        $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('uid', $this-&gt;getId()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('username', $this-&gt;getUsername()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('email', $this-&gt;getEmail()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Text('firstname', $this-&gt;getFirstname()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Text('lastname', $this-&gt;getLastname()));
        /* An unstored contents field as an aggregate 
          * of all data is no longer needed in *ZEND* Lucene 
          * But it's here.
          */
        $doc-&gt;addField(Zend_Search_Lucene_Field::Unstored('contents', implode(' ', array($this-&gt;getEmail(), $this-&gt;getFirstname(), $this-&gt;getLastname(), $this-&gt;getUsername())));
        return $doc;
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;We're really just dumping the relevant search terms into this document.  The beauty of keeping this code internalized in the &lt;code&gt;User&lt;/code&gt; class is we can reuse it later if we need to index a single &lt;code&gt;User&lt;/code&gt; at a time.&lt;/p&gt;

&lt;p&gt;A couple things to note.  &lt;code&gt;Zend_Search_Lucene_Field::Keyword&lt;/code&gt; allows us to store data that we can lookup later.  We store the &lt;code&gt;User::id&lt;/code&gt; in a field called &lt;code&gt;uid&lt;/code&gt; since &lt;code&gt;id&lt;/code&gt; is a reserved word for the index and we can't access it from &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;Zend Search Lucene&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In a batch script or a reindex action we can now just call &lt;code&gt;UserPeer::reindex()&lt;/code&gt; and have a working search index for our users.&lt;/p&gt;

&lt;div id=&quot;footnotes&quot;&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
        &lt;li id=&quot;fn_lucene_index1&quot;&gt;Storing things in &lt;code&gt;app.yml&lt;/code&gt; is great for indexes that don't need to be searched in multiple applications. &lt;a href=&quot;#fnr_lucene_index1&quot; class=&quot;footnoteBackLink&quot;  title=&quot;Jump back to footnote 1 in the text.&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/li&gt;
        &lt;li id=&quot;fn_lucene_index2&quot;&gt;
Since we're using a Lucene index, which has an open documented structure, we aren't limited to just using Zend Search Lucene or Apache Lucene (java).  We can mix and match and read and write to the same index file.  For very large indexes (65,000+ documents), I rewrote a Java application to index all the documents at once as PHP would time out during such a task.
&lt;a href=&quot;#fnr_lucene_index2&quot; class=&quot;footnoteBackLink&quot;  title=&quot;Jump back to footnote 2 in the text.&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>MinneBar</title>
   <link href="http://davedash.com/2007/04/21/minnebar/"/>
   <updated>2007-04-21T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/21/minnebar</id>
   <content type="html">&lt;p&gt;[tags]Minnebar, symfony, barcamp, frameworks, php, php5[/tags]&lt;/p&gt;

&lt;p&gt;I gave a quick overview of the symfony framework for PHP5 at &lt;a href=&quot;http://barcamp.org/MinneBar&quot;&gt;MinneBar&lt;/a&gt;.  If you have questions or comments about symfony or my talk, feel free to leave them here.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>MinneBar</title>
   <link href="http://davedash.com/2007/04/21/minnebar/"/>
   <updated>2007-04-21T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/21/minnebar</id>
   <content type="html">&lt;p&gt;[tags]Minnebar, symfony, barcamp, frameworks, php, php5[/tags]&lt;/p&gt;

&lt;p&gt;I gave a quick overview of the symfony framework for PHP5 at &lt;a href=&quot;http://barcamp.org/MinneBar&quot;&gt;MinneBar&lt;/a&gt;.  If you have questions or comments about symfony or my talk, feel free to leave them here.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Tips for symfony and Subversion</title>
   <link href="http://davedash.com/2007/04/17/tips-for-symfony-and-subversion/"/>
   <updated>2007-04-17T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/17/tips-for-symfony-and-subversion</id>
   <content type="html">&lt;p&gt;[tags]symfony, subversion[/tags]&lt;/p&gt;

&lt;p&gt;There's some tricks you can do to running a symfony project with subversion:&lt;/p&gt;

&lt;h3&gt;Ignoring files in &lt;code&gt;cache/&lt;/code&gt; and &lt;code&gt;log/&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;The first thing you can do (and this is well documented in the &lt;a href=&quot;http://www.symfony-project.com/askeet/1&quot;&gt;askeet tutorial&lt;/a&gt;) is ignore files in &lt;code&gt;cache/&lt;/code&gt; and &lt;code&gt;log/&lt;/code&gt;.  These files are specific to each instance of your app and don't contain anything that needs to be in version control.&lt;/p&gt;

&lt;p&gt;Run the following:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cd $SF_ROOT
rm -rf log/* cache/*
svn propedit svn:ignore log
svn propedit svn:ignore cache
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;svn propedit&lt;/code&gt; will bring up a text editor, in both instances you want to save the following:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;*
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Ignore other auto-generated files&lt;/h3&gt;

&lt;p&gt;Eric Sink wrote an excellent &lt;a href=&quot;http://www.ericsink.com/scm/source_control.html&quot;&gt;tutorial on source control&lt;/a&gt;.  In his chapter on &lt;a href=&quot;http://www.ericsink.com/scm/scm_repositories.html&quot;&gt;repositories&lt;/a&gt; he recommends checking in &lt;em&gt;only&lt;/em&gt; hand edited source code.  If a property file generates another file, check in the property file, not the auto-generated result.  This not only keeps your repository clean, it prevents a lot of unnecessary check-ins.&lt;/p&gt;

&lt;p&gt;If you use propel for your &lt;acronym title=&quot;Object Relational Mapping&quot;&gt;ORM&lt;/acronym&gt; layer there are a few files you can ignore using &lt;code&gt;svn propedit svn:ignore {dirname}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;$SF_ROOT/config&lt;/code&gt; we can ignore:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;*schema-transformed.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;These are &lt;code&gt;xml&lt;/code&gt; files that propel generates from &lt;code&gt;schema.xml&lt;/code&gt; (or &lt;code&gt;schema.yml&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;$SF_ROOT/data/sql&lt;/code&gt; we can ignore:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;lib.model.schema.sql
plugins.*.sql
sqldb.map
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;These are created from &lt;code&gt;schema.xml&lt;/code&gt; (or &lt;code&gt;schema.yml&lt;/code&gt;) as well.&lt;/p&gt;

&lt;p&gt;The real savings will come with your model.  The propel model creates customizable php classes in &lt;code&gt;lib/model&lt;/code&gt; which inherit from auto-generated files in &lt;code&gt;lib/om&lt;/code&gt; there are also auto-generated map files in `lib/map'&lt;/p&gt;

&lt;p&gt;We can run from &lt;code&gt;$SF_ROOT&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;svn propedit svn:ignore lib/model/om
svn propedit svn:ignore lib/model/map
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and enter&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;*
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;for both properties.&lt;/p&gt;

&lt;p&gt;If you've mistakenly checked in some of these files you will need to remove them from your repository via &lt;code&gt;svn delete&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Linking the symfony Library&lt;/h3&gt;

&lt;p&gt;I prefer to embed the symfony library into each of my symfony apps rather than relying on a shared PEAR library.  This lets me run multiple versions of symfony without much fuss.  With subversion we can use the &lt;code&gt;svn:externals&lt;/code&gt; property to directly link our app with the symfony subversion repository.&lt;/p&gt;

&lt;p&gt;At first this sounds like danger, but externals can be linked to specific revisions.  However, the &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; team tags their repository with version numbers.  To get this to work we need to do 3 things.  (&lt;strong&gt;UPDATE:&lt;/strong&gt; See Fabien's comment about using the lib/vendor directory)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Modify &lt;code&gt;config/config.php&lt;/code&gt; to look for symfony internally.  Just open it up and change it so it says this:
 &lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
 &amp;lt;?php
 $sf_symfony_lib_dir  = dirname(&lt;strong&gt;FILE&lt;/strong&gt;).'/../lib/symfony';
 $sf_symfony_data_dir = dirname(&lt;strong&gt;FILE&lt;/strong&gt;).'/../data/symfony';
 &lt;/textarea&gt;&lt;/div&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;svn propedit svn:externals lib&lt;/code&gt; from &lt;code&gt;$SF_ROOT&lt;/code&gt; and enter:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; symfony http://svn.symfony-project.com/tags/RELEASE_1_0_2/lib/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;or whatever version of symfony you want to link to, at the time of this post, &lt;code&gt;RELEASE_1_0_2&lt;/code&gt; is fairly fresh.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;svn propedit svn:externals data&lt;/code&gt; from &lt;code&gt;$SF_ROOT&lt;/code&gt; and enter:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; symfony http://svn.symfony-project.com/tags/RELEASE_1_0_2/data/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;or whatever version of symfony you want to link to, at the time of this post.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;Now when you do &lt;code&gt;svn update&lt;/code&gt; you'll have the &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; library all linked up.  Furthermore this keeps all the developers on the same version of &lt;code&gt;symfony&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also you may want to start running symfony using &lt;code&gt;./symfony&lt;/code&gt; versus &lt;code&gt;symfony&lt;/code&gt;.  The former looks at your configuration settings to determine which &lt;code&gt;symfony&lt;/code&gt; command to use, the latter is generally linked to your system wide command (which is generally the PEAR installed command).&lt;/p&gt;

&lt;h3&gt;Linking to symfony Plugins&lt;/h3&gt;

&lt;p&gt;I have my hands in a number of symfony plugins because I work on a lot of projects which tend to share a lot of similar functionality.  Many of the plugins are in early stages of development, so I find it helpful to have them linked from svn as well.  This way I can get the benefits of any new functionality and if the occasion should arise, I can contribute any useful changes I make.&lt;/p&gt;

&lt;p&gt;To link to the plugins you run &lt;code&gt;svn propedit svn:externals plugins&lt;/code&gt; and enter one plugin per line in the following format:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{plugin_name} -r{REVISION} {URL}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For one of my projects I use:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sfPrototypeTooltipPlugin http://svn.symfony-project.com/plugins/sfPrototypeTooltipPlugin
sfGuardPlugin http://svn.symfony-project.com/plugins/sfGuardPlugin
sfZendPlugin http://svn.symfony-project.com/plugins/sfZendPlugin
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I've omitted the revision, because I live dangerously and want to use the latest &lt;code&gt;$HEAD&lt;/code&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Tips for symfony and Subversion</title>
   <link href="http://davedash.com/2007/04/17/tips-for-symfony-and-subversion/"/>
   <updated>2007-04-17T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/17/tips-for-symfony-and-subversion</id>
   <content type="html">&lt;p&gt;[tags]symfony, subversion[/tags]&lt;/p&gt;

&lt;p&gt;There's some tricks you can do to running a symfony project with subversion:&lt;/p&gt;

&lt;h3&gt;Ignoring files in &lt;code&gt;cache/&lt;/code&gt; and &lt;code&gt;log/&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;The first thing you can do (and this is well documented in the &lt;a href=&quot;http://www.symfony-project.com/askeet/1&quot;&gt;askeet tutorial&lt;/a&gt;) is ignore files in &lt;code&gt;cache/&lt;/code&gt; and &lt;code&gt;log/&lt;/code&gt;.  These files are specific to each instance of your app and don't contain anything that needs to be in version control.&lt;/p&gt;

&lt;p&gt;Run the following:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cd $SF_ROOT
rm -rf log/* cache/*
svn propedit svn:ignore log
svn propedit svn:ignore cache
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;svn propedit&lt;/code&gt; will bring up a text editor, in both instances you want to save the following:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;*
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Ignore other auto-generated files&lt;/h3&gt;

&lt;p&gt;Eric Sink wrote an excellent &lt;a href=&quot;http://www.ericsink.com/scm/source_control.html&quot;&gt;tutorial on source control&lt;/a&gt;.  In his chapter on &lt;a href=&quot;http://www.ericsink.com/scm/scm_repositories.html&quot;&gt;repositories&lt;/a&gt; he recommends checking in &lt;em&gt;only&lt;/em&gt; hand edited source code.  If a property file generates another file, check in the property file, not the auto-generated result.  This not only keeps your repository clean, it prevents a lot of unnecessary check-ins.&lt;/p&gt;

&lt;p&gt;If you use propel for your &lt;acronym title=&quot;Object Relational Mapping&quot;&gt;ORM&lt;/acronym&gt; layer there are a few files you can ignore using &lt;code&gt;svn propedit svn:ignore {dirname}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;$SF_ROOT/config&lt;/code&gt; we can ignore:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;*schema-transformed.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;These are &lt;code&gt;xml&lt;/code&gt; files that propel generates from &lt;code&gt;schema.xml&lt;/code&gt; (or &lt;code&gt;schema.yml&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;$SF_ROOT/data/sql&lt;/code&gt; we can ignore:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;lib.model.schema.sql
plugins.*.sql
sqldb.map
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;These are created from &lt;code&gt;schema.xml&lt;/code&gt; (or &lt;code&gt;schema.yml&lt;/code&gt;) as well.&lt;/p&gt;

&lt;p&gt;The real savings will come with your model.  The propel model creates customizable php classes in &lt;code&gt;lib/model&lt;/code&gt; which inherit from auto-generated files in &lt;code&gt;lib/om&lt;/code&gt; there are also auto-generated map files in `lib/map'&lt;/p&gt;

&lt;p&gt;We can run from &lt;code&gt;$SF_ROOT&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;svn propedit svn:ignore lib/model/om
svn propedit svn:ignore lib/model/map
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and enter&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;*
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;for both properties.&lt;/p&gt;

&lt;p&gt;If you've mistakenly checked in some of these files you will need to remove them from your repository via &lt;code&gt;svn delete&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Linking the symfony Library&lt;/h3&gt;

&lt;p&gt;I prefer to embed the symfony library into each of my symfony apps rather than relying on a shared PEAR library.  This lets me run multiple versions of symfony without much fuss.  With subversion we can use the &lt;code&gt;svn:externals&lt;/code&gt; property to directly link our app with the symfony subversion repository.&lt;/p&gt;

&lt;p&gt;At first this sounds like danger, but externals can be linked to specific revisions.  However, the &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; team tags their repository with version numbers.  To get this to work we need to do 3 things.  (&lt;strong&gt;UPDATE:&lt;/strong&gt; See Fabien's comment about using the lib/vendor directory)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Modify &lt;code&gt;config/config.php&lt;/code&gt; to look for symfony internally.  Just open it up and change it so it says this:
 &lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
 &amp;lt;?php
 $sf_symfony_lib_dir  = dirname(&lt;strong&gt;FILE&lt;/strong&gt;).'/../lib/symfony';
 $sf_symfony_data_dir = dirname(&lt;strong&gt;FILE&lt;/strong&gt;).'/../data/symfony';
 &lt;/textarea&gt;&lt;/div&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;svn propedit svn:externals lib&lt;/code&gt; from &lt;code&gt;$SF_ROOT&lt;/code&gt; and enter:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; symfony http://svn.symfony-project.com/tags/RELEASE_1_0_2/lib/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;or whatever version of symfony you want to link to, at the time of this post, &lt;code&gt;RELEASE_1_0_2&lt;/code&gt; is fairly fresh.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;svn propedit svn:externals data&lt;/code&gt; from &lt;code&gt;$SF_ROOT&lt;/code&gt; and enter:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; symfony http://svn.symfony-project.com/tags/RELEASE_1_0_2/data/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;or whatever version of symfony you want to link to, at the time of this post.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;Now when you do &lt;code&gt;svn update&lt;/code&gt; you'll have the &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; library all linked up.  Furthermore this keeps all the developers on the same version of &lt;code&gt;symfony&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also you may want to start running symfony using &lt;code&gt;./symfony&lt;/code&gt; versus &lt;code&gt;symfony&lt;/code&gt;.  The former looks at your configuration settings to determine which &lt;code&gt;symfony&lt;/code&gt; command to use, the latter is generally linked to your system wide command (which is generally the PEAR installed command).&lt;/p&gt;

&lt;h3&gt;Linking to symfony Plugins&lt;/h3&gt;

&lt;p&gt;I have my hands in a number of symfony plugins because I work on a lot of projects which tend to share a lot of similar functionality.  Many of the plugins are in early stages of development, so I find it helpful to have them linked from svn as well.  This way I can get the benefits of any new functionality and if the occasion should arise, I can contribute any useful changes I make.&lt;/p&gt;

&lt;p&gt;To link to the plugins you run &lt;code&gt;svn propedit svn:externals plugins&lt;/code&gt; and enter one plugin per line in the following format:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{plugin_name} -r{REVISION} {URL}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For one of my projects I use:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sfPrototypeTooltipPlugin http://svn.symfony-project.com/plugins/sfPrototypeTooltipPlugin
sfGuardPlugin http://svn.symfony-project.com/plugins/sfGuardPlugin
sfZendPlugin http://svn.symfony-project.com/plugins/sfZendPlugin
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I've omitted the revision, because I live dangerously and want to use the latest &lt;code&gt;$HEAD&lt;/code&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>sfZendPlugin</title>
   <link href="http://davedash.com/2007/04/10/sfzendplugin/"/>
   <updated>2007-04-10T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/10/sfzendplugin</id>
   <content type="html">&lt;p&gt;[tags]Zend, Zend Search Lucene, Search, Lucene, php, symfony, zsl, plugins[/tags]&lt;/p&gt;

&lt;p&gt;I originally intended to rewrite &lt;a href=&quot;http://spindrop.us/2006/08/25/using-zend-search-lucene-in-a-symfony-app/&quot;&gt;my Zend Search Lucene tutorial&lt;/a&gt;, but &lt;a href=&quot;http://archivemati.ca/2007/03/08/zend-search-lucene-symfony-and-the-ica-atom-application/&quot;&gt;Peter Van Garderen&lt;/a&gt; covered the bulk of what's changed and I was too busy developing search functionality for &lt;a href=&quot;http://lyro.com/&quot;&gt;lyro.com&lt;/a&gt; (not to mention finding inconsistencies with the Zend Search Lucene port and Lucene) to finish the tutorial.  So I broke it up into smaller pieces.&lt;/p&gt;

&lt;p&gt;I packaged &lt;a href=&quot;http://framework.zend.com/&quot;&gt;Zend Framework&lt;/a&gt; into a &lt;a href=&quot;http://www.symfony-project.com/trac/browser/plugins/sfZendPlugin&quot;&gt;symfony plugin&lt;/a&gt;.  &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; is easily extended using plugins.&lt;/p&gt;

&lt;p&gt;You can obtain this from subversion with the following command (from your &lt;code&gt;/plugins&lt;/code&gt; directory):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;svn export http://svn.symfony-project.com/plugins/sfZendPlugin
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; has a &lt;a href=&quot;http://www.symfony-project.com/book/trunk/17-Extending-Symfony#Bridges%20to%20Other%20Framework%20Components&quot;&gt;Zend Framework Bridge&lt;/a&gt; which let's us autoload the framework by adding the following to &lt;code&gt;settings.yml&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;.settings:
  zend_lib_dir:   %SF_ROOT_DIR%/plugins/sfZendPlugin/lib
  autoloading_functions:
    - [sfZendFrameworkBridge, autoload]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;First we define &lt;code&gt;sf_zend_lib_dir&lt;/code&gt; to be in our plugin's &lt;code&gt;lib&lt;/code&gt; directory.  Then we autoload the bridge framework.&lt;/p&gt;

&lt;p&gt;After setting this up, all the Zend classes will be available and auto-loaded from elsewhere in your code.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>sfZendPlugin</title>
   <link href="http://davedash.com/2007/04/10/sfzendplugin/"/>
   <updated>2007-04-10T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/10/sfzendplugin</id>
   <content type="html">&lt;p&gt;[tags]Zend, Zend Search Lucene, Search, Lucene, php, symfony, zsl, plugins[/tags]&lt;/p&gt;

&lt;p&gt;I originally intended to rewrite &lt;a href=&quot;http://spindrop.us/2006/08/25/using-zend-search-lucene-in-a-symfony-app/&quot;&gt;my Zend Search Lucene tutorial&lt;/a&gt;, but &lt;a href=&quot;http://archivemati.ca/2007/03/08/zend-search-lucene-symfony-and-the-ica-atom-application/&quot;&gt;Peter Van Garderen&lt;/a&gt; covered the bulk of what's changed and I was too busy developing search functionality for &lt;a href=&quot;http://lyro.com/&quot;&gt;lyro.com&lt;/a&gt; (not to mention finding inconsistencies with the Zend Search Lucene port and Lucene) to finish the tutorial.  So I broke it up into smaller pieces.&lt;/p&gt;

&lt;p&gt;I packaged &lt;a href=&quot;http://framework.zend.com/&quot;&gt;Zend Framework&lt;/a&gt; into a &lt;a href=&quot;http://www.symfony-project.com/trac/browser/plugins/sfZendPlugin&quot;&gt;symfony plugin&lt;/a&gt;.  &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; is easily extended using plugins.&lt;/p&gt;

&lt;p&gt;You can obtain this from subversion with the following command (from your &lt;code&gt;/plugins&lt;/code&gt; directory):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;svn export http://svn.symfony-project.com/plugins/sfZendPlugin
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; has a &lt;a href=&quot;http://www.symfony-project.com/book/trunk/17-Extending-Symfony#Bridges%20to%20Other%20Framework%20Components&quot;&gt;Zend Framework Bridge&lt;/a&gt; which let's us autoload the framework by adding the following to &lt;code&gt;settings.yml&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;.settings:
  zend_lib_dir:   %SF_ROOT_DIR%/plugins/sfZendPlugin/lib
  autoloading_functions:
    - [sfZendFrameworkBridge, autoload]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;First we define &lt;code&gt;sf_zend_lib_dir&lt;/code&gt; to be in our plugin's &lt;code&gt;lib&lt;/code&gt; directory.  Then we autoload the bridge framework.&lt;/p&gt;

&lt;p&gt;After setting this up, all the Zend classes will be available and auto-loaded from elsewhere in your code.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Caching REST with sfFunctionCache</title>
   <link href="http://davedash.com/2007/04/07/caching-rest-with-sffunctioncache/"/>
   <updated>2007-04-07T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/07/caching-rest-with-sffunctioncache</id>
   <content type="html">&lt;p&gt;[tags]geocoding, caching, REST, symfony, Cache_Lite, php, cache, sfFunctionCache[/tags]&lt;/p&gt;

&lt;p&gt;For &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; we do a lot of geocoding.  To facilitate we use &lt;a href=&quot;http://developer.yahoo.com/maps/rest/V1/geocode.html&quot;&gt;Yahoo! Geocoding API&lt;/a&gt;.  This helps us normalize data, obtain latitude and longitude, interpret location specific searches.&lt;/p&gt;

&lt;p&gt;These &lt;acronym title=&quot;REpresentational State Transfer&quot;&gt;REST&lt;/acronym&gt; queries happen a lot and will continue to happen, but this data that Yahoo! provides is fairly static.  We're basically querying a database of sorts.  So it makes sense that we should cache this data.&lt;/p&gt;

&lt;p&gt;We'll demonstrate how to cache these queries using &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;'s &lt;a href=&quot;http://www.symfony-project.com/book/trunk/18-Performance#Caching%20the%20Result%20of%20a%20Function%20Call&quot;&gt;&lt;code&gt;sfFunctionCache&lt;/code&gt;&lt;/a&gt; class.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;I wrote a wrapper (I'll release it as a plugin if requested) for the &lt;a href=&quot;http://developer.yahoo.com/maps/rest/V1/geocode.html&quot;&gt;Geocoding API&lt;/a&gt;, the bulk of the work (the &lt;acronym title=&quot;REpresentational State Transfer&quot;&gt;REST&lt;/acronym&gt; call) occurs in a function called &lt;code&gt;doQueryGIS&lt;/code&gt;:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
        public static function doQueryGIS($location)
        {
            $url               = sfConfig::get('app_yahoo_geocode');

            $query             = array();
            $query['appid']    = sfConfig::get('app_yahoo_app_id');
            $query['location'] = $location;
            $query['output']   = 'php';

            $url .= '?' . http_build_query($query,null,'&amp;');    
              $curl = curl_init($url);
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
            $response = curl_exe c($curl); 
              // note there should be no space between curl_exe and c($curl) 
              // wordpress is just dumb
        

            if ($response != 'Array') 
            {
                return unserialize($response);
            } 
            else 
            {
                throw new sfException('Yahoo! GeoCoder does not understand: &quot;'. $location . &quot;\&quot;\n&quot;);
            }
            return false;   
            
            
        }

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The call to this function is always wrapped with &lt;code&gt;queryGIS&lt;/code&gt;:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
      protected function queryGIS()
        {
            $function_cache_dir = sfConfig::get('sf_cache_dir').'/function';
            $cache = new sfFunctionCache($function_cache_dir);
            $this-&gt;rawData = $cache-&gt;call(array('YahooGeo','doQueryGIS'), $this-&gt;rawLocation);
            return $this-&gt;rawData;
            
        }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;This wrapper creates a &lt;code&gt;sfFunctionCache&lt;/code&gt; objet and calls the function and caches it for subsequent queries.&lt;/p&gt;

&lt;p&gt;What this means is once Yahoo! teaches &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; that India is located at (25.42&amp;deg;, 77.830002&amp;deg;) and that the precision is 'country' we remember it in the future.&lt;/p&gt;

&lt;p&gt;These features will be incorporated into future versions of &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Caching REST with sfFunctionCache</title>
   <link href="http://davedash.com/2007/04/07/caching-rest-with-sffunctioncache/"/>
   <updated>2007-04-07T00:00:00-07:00</updated>
   <id>http://davedash.com/2007/04/07/caching-rest-with-sffunctioncache</id>
   <content type="html">&lt;p&gt;[tags]geocoding, caching, REST, symfony, Cache_Lite, php, cache, sfFunctionCache[/tags]&lt;/p&gt;

&lt;p&gt;For &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; we do a lot of geocoding.  To facilitate we use &lt;a href=&quot;http://developer.yahoo.com/maps/rest/V1/geocode.html&quot;&gt;Yahoo! Geocoding API&lt;/a&gt;.  This helps us normalize data, obtain latitude and longitude, interpret location specific searches.&lt;/p&gt;

&lt;p&gt;These &lt;acronym title=&quot;REpresentational State Transfer&quot;&gt;REST&lt;/acronym&gt; queries happen a lot and will continue to happen, but this data that Yahoo! provides is fairly static.  We're basically querying a database of sorts.  So it makes sense that we should cache this data.&lt;/p&gt;

&lt;p&gt;We'll demonstrate how to cache these queries using &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;'s &lt;a href=&quot;http://www.symfony-project.com/book/trunk/18-Performance#Caching%20the%20Result%20of%20a%20Function%20Call&quot;&gt;&lt;code&gt;sfFunctionCache&lt;/code&gt;&lt;/a&gt; class.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;I wrote a wrapper (I'll release it as a plugin if requested) for the &lt;a href=&quot;http://developer.yahoo.com/maps/rest/V1/geocode.html&quot;&gt;Geocoding API&lt;/a&gt;, the bulk of the work (the &lt;acronym title=&quot;REpresentational State Transfer&quot;&gt;REST&lt;/acronym&gt; call) occurs in a function called &lt;code&gt;doQueryGIS&lt;/code&gt;:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
        public static function doQueryGIS($location)
        {
            $url               = sfConfig::get('app_yahoo_geocode');

            $query             = array();
            $query['appid']    = sfConfig::get('app_yahoo_app_id');
            $query['location'] = $location;
            $query['output']   = 'php';

            $url .= '?' . http_build_query($query,null,'&amp;');    
              $curl = curl_init($url);
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
            $response = curl_exe c($curl); 
              // note there should be no space between curl_exe and c($curl) 
              // wordpress is just dumb
        

            if ($response != 'Array') 
            {
                return unserialize($response);
            } 
            else 
            {
                throw new sfException('Yahoo! GeoCoder does not understand: &quot;'. $location . &quot;\&quot;\n&quot;);
            }
            return false;   
            
            
        }

&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The call to this function is always wrapped with &lt;code&gt;queryGIS&lt;/code&gt;:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
      protected function queryGIS()
        {
            $function_cache_dir = sfConfig::get('sf_cache_dir').'/function';
            $cache = new sfFunctionCache($function_cache_dir);
            $this-&gt;rawData = $cache-&gt;call(array('YahooGeo','doQueryGIS'), $this-&gt;rawLocation);
            return $this-&gt;rawData;
            
        }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;This wrapper creates a &lt;code&gt;sfFunctionCache&lt;/code&gt; objet and calls the function and caches it for subsequent queries.&lt;/p&gt;

&lt;p&gt;What this means is once Yahoo! teaches &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; that India is located at (25.42&amp;deg;, 77.830002&amp;deg;) and that the precision is 'country' we remember it in the future.&lt;/p&gt;

&lt;p&gt;These features will be incorporated into future versions of &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Parsing a list of Key:Value pairs</title>
   <link href="http://davedash.com/2007/02/24/parsing-a-list-of-kv-pairs/"/>
   <updated>2007-02-24T00:00:00-08:00</updated>
   <id>http://davedash.com/2007/02/24/parsing-a-list-of-kv-pairs</id>
   <content type="html">&lt;p&gt;[tags]best practices,php,openID[/tags]
I'm working on implementing &lt;a href=&quot;http://openid.net/&quot;&gt;openID&lt;/a&gt; for &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; and for use in &lt;a href=&quot;http://www.symfony-project.com/&quot;&gt;symfony&lt;/a&gt; apps.  One thing I was having trouble with was parsing key value pairs, which is one of the requirements to reading responses.  It's a fairly easy task, but &lt;a href=&quot;http://php.net/&quot;&gt;PHP&lt;/a&gt; offers so many ways to do this.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://openid.net/&quot;&gt;openID&lt;/a&gt; calls for the following &lt;a href=&quot;http://openid.net/specs/openid-authentication-1_1.html#anchor32&quot;&gt;Key-Value format&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Lines of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; some_key:some value&lt;/li&gt;
&lt;li&gt; There MUST NOT be a space before or after the colon.&lt;/li&gt;
&lt;li&gt; Newline characters MUST be Unix-style, just ASCII character 10 (&quot;\n&quot;).&lt;/li&gt;
&lt;li&gt; Newlines MUST BE at end of each line as well as between lines.&lt;/li&gt;
&lt;li&gt; MIME type is unspecified, but text/plain is RECOMMENDED.&lt;/li&gt;
&lt;li&gt; Character encoding MUST BE UTF-8.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;So here is my attempt at parsing something like this as efficiently and error free as possible:&lt;/p&gt;

&lt;!--more--&gt;


&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
function splitKV($response) 
{
    $r = array();
    preg_match_all('|^\s*([^:]+):([^:\n]+)[ ]*$|m', $kvs, $matches);
    for($i = 0; $i &lt; count($matches[0]); $i++) {
        $r[$matches[1][$i]] = $matches[2][$i];
    }
    return $r;
}
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;I wrote this function as I was writing the post... and it came out way faster than my previous implementations using &lt;code&gt;strtok&lt;/code&gt; or a combination of &lt;code&gt;explode&lt;/code&gt; and &lt;code&gt;trim&lt;/code&gt;.  Which is good to hear since I do use &lt;code&gt;preg_&lt;/code&gt; functions quite a bit in PHP and they definitely have their place.&lt;/p&gt;

&lt;p&gt;I'm curious if people have found a faster way of parsing through a string of Key-Value pairs.  I'll run it in a test harness and stand corrected if someone comes through with something faster ;).&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Not taking frameworks for granted</title>
   <link href="http://davedash.com/2006/11/21/not-taking-frameworks-for-granted/"/>
   <updated>2006-11-21T00:00:00-08:00</updated>
   <id>http://davedash.com/2006/11/21/not-taking-frameworks-for-granted</id>
   <content type="html">&lt;p&gt;One of my clients approached me with a relatively easy project.  She gave me a log file of PHP errors and I was supposed to fix her scripts.  I fixed about 100+ different errors in a few hours.  It was fairly straightforward.&lt;/p&gt;

&lt;p&gt;Throughout the site I could understand the previous developer and the choices he or she made for better or worse.  It did look like a struggle however.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;Ultimately it felt that there were some fairly simple things that each script needed to do, but each task was a challenge.  Validating forms, storing data across pages, decorating the site, interacting with the database.  Everything seemed very kludged together.&lt;/p&gt;

&lt;p&gt;I realized that this is exactly how I used to write code, sure... my way of course was better and more logical, yada, yada, yada... but ultimately I was there before.&lt;/p&gt;

&lt;p&gt;My client noted that these scripts were made from another contract programmer, and then a light-bulb went on... frameworks (whether it be &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, &lt;acronym title=&quot;Ruby On Rails&quot;&gt;ROR&lt;/acronym&gt;, Django, CakePHP, etc) help iron out and standardize these tasks.&lt;/p&gt;

&lt;p&gt;Since I know &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; best, I'll cover what I think could have helped in this last project.  I'm sure other major frameworks have their equivalents.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Form validation&lt;/strong&gt;: &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; lets you define form validation in a very simple manner.  The validation logic is also separate from the rest of the code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storing Data&lt;/strong&gt;: Without a framework, you generally have to rely on the &lt;code&gt;$_SESSION&lt;/code&gt; array in PHP.  While very useful and easy to use, storing parameters and attributes to a user object is done a lot more cleanly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;decorating the site&lt;/strong&gt;: My biggest problem was with each page I had,, I used to have to call headers, sidebars, etc, etc.  &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;'s layout system was a boon.  I had a common layout for all pages (maybe a few alternates) and hooks inside them if they needed to be adjusted.  Then the various actions had their own seperate templates that were injected into the common layout.  It made adding new pages easy, since I didn't need to remember &lt;code&gt;header()&lt;/code&gt; and &lt;code&gt;footer()&lt;/code&gt; functions for each and every page.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interacting with the database&lt;/strong&gt;: &lt;a href=&quot;http://spindrop.us/2006/08/07/how-object-relational-mapping-saves-time-and-makes-your-code-sexy/&quot;&gt;I've covered before the benefits of ORM&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Not only do the bulk of these problems disappear with a framework, a lot of the difficulties of switching developers melt away.  If you tell me, a developer, that I'm walking into a project made with a framework, I can learn about the framework and be able to understand its ins and outs.&lt;/p&gt;

&lt;p&gt;If you just tell me it's written in PHP, chances are I'm going to want to do things my own way.  It's hard to understand the logic that another programmer was using so we fall back to standards whether they are your own or borrowed.&lt;/p&gt;

&lt;p&gt;When we use a framework, we can find some mutually agreed upon standards and usually people who specialize in that framework and are willing to help.  So my advice: stick to frameworks.  The coding style will be no worse than the whims of a programmer, but at best it'll be something that anyone can pick up.  The general case is that even a bad coder can only do so much damage within a framework.  The bullet points I covered above will cut down on development time tremendously.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Not taking frameworks for granted</title>
   <link href="http://davedash.com/2006/11/21/not-taking-frameworks-for-granted/"/>
   <updated>2006-11-21T00:00:00-08:00</updated>
   <id>http://davedash.com/2006/11/21/not-taking-frameworks-for-granted</id>
   <content type="html">&lt;p&gt;One of my clients approached me with a relatively easy project.  She gave me a log file of PHP errors and I was supposed to fix her scripts.  I fixed about 100+ different errors in a few hours.  It was fairly straightforward.&lt;/p&gt;

&lt;p&gt;Throughout the site I could understand the previous developer and the choices he or she made for better or worse.  It did look like a struggle however.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;Ultimately it felt that there were some fairly simple things that each script needed to do, but each task was a challenge.  Validating forms, storing data across pages, decorating the site, interacting with the database.  Everything seemed very kludged together.&lt;/p&gt;

&lt;p&gt;I realized that this is exactly how I used to write code, sure... my way of course was better and more logical, yada, yada, yada... but ultimately I was there before.&lt;/p&gt;

&lt;p&gt;My client noted that these scripts were made from another contract programmer, and then a light-bulb went on... frameworks (whether it be &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, &lt;acronym title=&quot;Ruby On Rails&quot;&gt;ROR&lt;/acronym&gt;, Django, CakePHP, etc) help iron out and standardize these tasks.&lt;/p&gt;

&lt;p&gt;Since I know &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; best, I'll cover what I think could have helped in this last project.  I'm sure other major frameworks have their equivalents.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Form validation&lt;/strong&gt;: &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; lets you define form validation in a very simple manner.  The validation logic is also separate from the rest of the code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storing Data&lt;/strong&gt;: Without a framework, you generally have to rely on the &lt;code&gt;$_SESSION&lt;/code&gt; array in PHP.  While very useful and easy to use, storing parameters and attributes to a user object is done a lot more cleanly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;decorating the site&lt;/strong&gt;: My biggest problem was with each page I had,, I used to have to call headers, sidebars, etc, etc.  &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;'s layout system was a boon.  I had a common layout for all pages (maybe a few alternates) and hooks inside them if they needed to be adjusted.  Then the various actions had their own seperate templates that were injected into the common layout.  It made adding new pages easy, since I didn't need to remember &lt;code&gt;header()&lt;/code&gt; and &lt;code&gt;footer()&lt;/code&gt; functions for each and every page.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interacting with the database&lt;/strong&gt;: &lt;a href=&quot;http://spindrop.us/2006/08/07/how-object-relational-mapping-saves-time-and-makes-your-code-sexy/&quot;&gt;I've covered before the benefits of ORM&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Not only do the bulk of these problems disappear with a framework, a lot of the difficulties of switching developers melt away.  If you tell me, a developer, that I'm walking into a project made with a framework, I can learn about the framework and be able to understand its ins and outs.&lt;/p&gt;

&lt;p&gt;If you just tell me it's written in PHP, chances are I'm going to want to do things my own way.  It's hard to understand the logic that another programmer was using so we fall back to standards whether they are your own or borrowed.&lt;/p&gt;

&lt;p&gt;When we use a framework, we can find some mutually agreed upon standards and usually people who specialize in that framework and are willing to help.  So my advice: stick to frameworks.  The coding style will be no worse than the whims of a programmer, but at best it'll be something that anyone can pick up.  The general case is that even a bad coder can only do so much damage within a framework.  The bullet points I covered above will cut down on development time tremendously.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>OpenID</title>
   <link href="http://davedash.com/2006/10/03/openid/"/>
   <updated>2006-10-03T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/10/03/openid</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://openid.net/&quot;&gt;OpenID&lt;/a&gt; is a wonderful concept.  If I visit a web site, Acme Widgets, I only need to supply a &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt; that belongs to me in order to log in.  The web site at that &lt;acronym title=&quot;URL&quot;&gt;URL&lt;/acronym&gt; will provide a place where I can authorize Acme Widgets to log me in with this id.&lt;/p&gt;

&lt;p&gt;The benefit of this type of identity system is now you don't need to create new username's and passwords for each site you'd like to use.  We initially began &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; with just &lt;a href=&quot;http://openid.net/&quot;&gt;OpenID&lt;/a&gt; for that reason.  Now we can target places like &lt;a href=&quot;http://livejournal.com/&quot;&gt;livejournal&lt;/a&gt; and make it exceptionally easy for their users to register with our site.&lt;/p&gt;

&lt;p&gt;As of this writing, and even more robust system is being developed, &lt;a href=&quot;http://whobar.org/&quot;&gt;whobar&lt;/a&gt;.  &lt;a href=&quot;http://whobar.org/&quot;&gt;Whobar&lt;/a&gt; supports multiple identification sites, not just &lt;a href=&quot;http://openid.net/&quot;&gt;OpenID&lt;/a&gt;.  Rather than walking through explaining how to do a sign-in system that integrates &lt;a href=&quot;http://openid.net/&quot;&gt;OpenID&lt;/a&gt;, I'll direct people to &lt;a href=&quot;http://whobar.org/&quot;&gt;whobar&lt;/a&gt;.  It is in the plans for &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; to integrate &lt;a href=&quot;http://whobar.org/&quot;&gt;whobar&lt;/a&gt; and once that happens you can expect a shiny tutorial and/or plugin for symfony.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Coming soon to reviewsby.us</title>
   <link href="http://davedash.com/2006/10/02/coming-soon-to-reviewsbyus/"/>
   <updated>2006-10-02T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/10/02/coming-soon-to-reviewsbyus</id>
   <content type="html">&lt;p&gt;In August I took a break from &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; only to be plagued by spam.  In September, I relinquished portions of the project planning to my wife.  We haven't released anything publicly, yet, but there's a lot in development.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I updated the development framework to &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; 1.0 alpha and took care of a whole slew of bugs.&lt;/li&gt;
&lt;li&gt;Katie and I came up with a &lt;a href=&quot;http://flickr.com/photos/davedash/251518309/&quot;&gt;wireframe&lt;/a&gt; that details some of the upcoming changes.&lt;/li&gt;
&lt;li&gt;I upgraded the user logic to take advantage of sfGuardUser, a user management plugin for symfony.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;I'm in progress of writing location specific searches.  I'm slow to implement.  It seems that this month is far busier than I'd like, and I can rarely get in a block of enough time to just crank this out.  The problem with geographic-specific searches is mySQL supports those types of queries, but it's not as easy as I'd like.  Zend Search Lucene with some support from PHP, however, may yield some promising results.  As always, I'll share my findings in a forthcoming tutorial.&lt;/p&gt;

&lt;p&gt;Anyway, no visible updates on the actual site, since I didn't want to put alpha software on the live site.  I'm sure by next month symfony will be ready.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Cropping Images using DHTML (Prototype) and symfony</title>
   <link href="http://davedash.com/2006/09/16/cropping-images-using-dhtml-prototype-and-symfony/"/>
   <updated>2006-09-16T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/09/16/cropping-images-using-dhtml-prototype-and-symfony</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://demos.spindrop.us/image_cropper/&quot;&gt;&lt;img src=&quot;http://demos.spindrop.us/image_cropper/images/screenshot.png&quot; style=&quot;float:left;margin-right:1em&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Like many of my tutorials, you don't &lt;em&gt;need&lt;/em&gt; &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, just &lt;a href=&quot;http://php.net/&quot;&gt;PHP&lt;/a&gt;.  However, I develop in &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; and take advantage of the &lt;acronym title=&quot;Model Viewer Controller&quot;&gt;MVC&lt;/acronym&gt;-support that it offers.&lt;/p&gt;

&lt;p&gt;Years ago when I was working on a photo gallery for &lt;a href=&quot;http://davedash.com/&quot;&gt;davedash.com&lt;/a&gt; I got the art of making tumbnails down fairly well.  It was automated and didn't allow for specifying how the thumbnail should be made.  With dozens of photos (which was a lot back then), when would I find that kind of time.&lt;/p&gt;

&lt;p&gt;Flashback to today, for &lt;a href=&quot;http://workface.com/&quot;&gt;my company&lt;/a&gt;... we want users with avatars... but nothing too large.  Maybe a nice 80x80 picture.  Well the coolest &lt;acronym title=&quot;User Interface&quot;&gt;UI&lt;/acronym&gt; I've seen was Apple's Address Book which let you use this slider mechanism to crop a fixed sized image from a larger image.&lt;/p&gt;

&lt;p&gt;Here's a &lt;a href=&quot;http://demos.spindrop.us/image_cropper/&quot;&gt;demo&lt;/a&gt;.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;Overview&lt;/h3&gt;

&lt;p&gt;The front-end &lt;acronym title=&quot;Graphical User Interface&quot;&gt;GUI&lt;/acronym&gt; is based on code from &lt;a href=&quot;http://digg.com/&quot;&gt;digg&lt;/a&gt; which is based on the look and feel (as near as I can tell) from Apple.&lt;/p&gt;

&lt;p&gt;The &lt;acronym title=&quot;Graphical User Interface&quot;&gt;GUI&lt;/acronym&gt; provides a clever visual way of telling the server how to chop the image.  The gist is this, sliding the image around and zooming in and out change a few form values that get passed to another script which uses this data to produce the image.&lt;/p&gt;

&lt;h3&gt;Frontend: What would you like to crop?&lt;/h3&gt;

&lt;p&gt;In this tutorial, we're going to be cropping an 80x80 avatar from an uploaded image.  The front-end requires the correct mix of Javascript, &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt;, &lt;acronym title=&quot;HypterText Markup Languag&quot;&gt;HTML&lt;/acronym&gt; and images.  The Javascript sets up the initial placements of the image and the controls.  The &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; presents some necessary styling.  The images makeup some of the controls.  The &lt;acronym title=&quot;HyperText Markup Language&quot;&gt;HTML&lt;/acronym&gt; glues everything together.&lt;/p&gt;

&lt;h4&gt;&lt;acronym title=&quot;HyperText Markup Language&quot;&gt;HTML&lt;/acronym&gt;&lt;/h4&gt;

&lt;p&gt;Let's work on our &lt;acronym title=&quot;HyperText Markup Language&quot;&gt;HTML&lt;/acronym&gt; first.  Since I used &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, I created a &lt;code&gt;crop&lt;/code&gt; action for a &lt;code&gt;userpics&lt;/code&gt; module.  So in our &lt;code&gt;cropSuccess.php&lt;/code&gt; template:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;div id=&quot;ava&quot;&amp;gt;
    &amp;lt;?php echo form_tag(&quot;userpics/crop&quot;) ?&amp;gt;
        &amp;lt;div id=&quot;ava_img&quot;&amp;gt;
            &amp;lt;div id=&quot;ava_overlay&quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div id=&quot;ava_drager&quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;img src=&quot;&amp;lt;?php echo $image ?&amp;gt;&quot; id=&quot;avatar&quot; /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div id=&quot;ava_slider&quot;&amp;gt;&amp;lt;div id=&quot;ava_handle&quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;input type=&quot;hidden&quot; id=&quot;ava_width&quot; name=&quot;width&quot; value=&quot;80&quot; /&amp;gt;
        &amp;lt;input type=&quot;hidden&quot; id=&quot;ava_x&quot; name=&quot;x&quot; value=&quot;100&quot; /&amp;gt;
        &amp;lt;input type=&quot;hidden&quot; id=&quot;ava_y&quot; name=&quot;y&quot; value=&quot;100&quot; /&amp;gt;
        &amp;lt;input type=&quot;hidden&quot; id=&quot;ava_image&quot; name=&quot;file&quot; value=&quot;&amp;lt;?php echo $image ?&amp;gt;&quot; /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;input type=&quot;submit&quot; name=&quot;submit&quot; id=&quot;ava_submit&quot; value=&quot;Crop&quot; style=&quot;width: auto; font-size: 105%; font-weight: bold; margin: 1em 0;&quot; /&amp;gt;
    &amp;lt;/form&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Right now a lot of this doesn't quite make sense.  If you attempt to render it, you will just see only the image.  As we add the corresponding &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; and images it will make some more sense.&lt;/p&gt;

&lt;h4&gt;&lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; and corresponding images&lt;/h4&gt;

&lt;p&gt;We'll go through each style individually and explain what purpose it serves in terms of the &lt;acronym title=&quot;Graphical User Interface&quot;&gt;GUI&lt;/acronym&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#ava&lt;/code&gt; is our container.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#ava {
    border: 1px solid gray;
     width: 200px;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;#ava_img&lt;/code&gt; is the area that contains our image.  Our window for editing this image 200x200 pixels.  If we drag out image out of bounds we just want the overflowing image to be clipped.  We also want our position to be relative so any child elements can be positioned absolutely with respect to &lt;code&gt;#ava_img&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#ava_img {
    width: 200px;
    height: 200px;
    overflow: hidden;
    position: relative;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;div style=&quot;float:left;margin: 1em 1em 1em 0&quot;&gt;
    &lt;img src=&quot;http://demos.spindrop.us/image_cropper/images/overlay.png&quot; alt=&quot;overlay&quot; /&gt;
&lt;/div&gt;


&lt;p&gt;&lt;code&gt;#ava_overlay&lt;/code&gt; is a window we use to see what exactly will be our avatar.  If it's in the small 80x80 window in the center of the image, then it's part of the avatar.  If it's in the fuzzy region, then it's getting cropped out.  This overlay of course needs to be positioned absolutely.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#ava_overlay {
    width: 200px;
    height: 200px;
    position: absolute;
    top: 0px;
    left: 0px;
    background: url('/images/overlay.png');
    z-index: 50;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;#ava_drager&lt;/code&gt; is probably the least intuitive element (Heck, I'm not even sure if I've even got it right).  In &lt;a href=&quot;http://demos.spindrop.us/image_cropper/&quot;&gt;our demo&lt;/a&gt; you're not actually dragging the image, because you can drag anywhere within the &lt;code&gt;#ava_img&lt;/code&gt; container and move the image around.  You're using dragging an invisible handle.  It's a 400x400 pixel square that can be dragged all over the container and thusly move the image as needed.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#ava_drager {
    width: 400px;
    height: 400px;
    position: absolute;
    z-index: 100;
    color: #fff;
    cursor: move;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;#avatar&lt;/code&gt; is our image, and since it will be moving all around the window, it requires absolute positioning.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#avatar {
    position: absolute;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;div style=&quot;float:left;margin: 1em 1em 1em 0&quot;&gt;
    &lt;img src=&quot;http://demos.spindrop.us/image_cropper/images/slider_back.png&quot; alt=&quot;overlay&quot; /&gt;
&lt;/div&gt;


&lt;div style=&quot;float:left;margin-left: 1em 1em 1em 0;&quot;&gt;
    &lt;img src=&quot;http://demos.spindrop.us/image_cropper/images/handle.png&quot; alt=&quot;overlay&quot; /&gt;
&lt;/div&gt;


&lt;p&gt;&lt;code&gt;#ava_slider&lt;/code&gt; and &lt;code&gt;#ava_handle&lt;/code&gt; are our slider components.  They should be self-explanatory.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#ava_slider {
    width: 200px;
    height: 27px;
    background: #eee;
    position: relative;
    border-top: 1px solid gray; 
    background: url('/images/slider_back.png');
}
#ava_handle {
    width: 19px;
    height: 20px;
    background: blue;
    position: absolute;
    background: url('/images/handle.png');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;Internet Explorer&lt;/h5&gt;

&lt;p&gt;&lt;acronym title=&quot;Portable Network Graphics&quot;&gt;PNG&lt;/acronym&gt; do not work so well in Internet Explorer, but there is a small trick, adding these components into a style sheet that only IE can read will make things work:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#ava_overlay {
  background: none;
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/images/ui/cropper/overlay.png', sizingMethod='crop');
}

#ava_handle {
  background: none;
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/images/ui/cropper/handle.png', sizingMethod='crop');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;The Javascript&lt;/h4&gt;

&lt;p&gt;The Javascript is actually not as complicated as you'd expect thanks to the wonder of &lt;a href=&quot;http://prototype.conio.net/&quot;&gt;prototype&lt;/a&gt;.  This framework provides so much so easily.  You'll need to include &lt;a href=&quot;http://prototype.conio.net/&quot;&gt;prototype.js&lt;/a&gt; and &lt;a href=&quot;http://boring.youngpup.net/2001/domdrag/project&quot;&gt;dom-drag.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So let's take a look.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;script type=&quot;text/javascript&quot; language=&quot;javascript&quot; charset=&quot;utf-8&quot;&amp;gt;
// &amp;lt;![CDATA[
function setupAva() {
    if ($(&quot;avatar&quot;)) {
        var handle = $(&quot;ava_handle&quot;);
        var avatar = $(&quot;avatar&quot;);
        var drager = $(&quot;ava_drager&quot;);
        var slider = $(&quot;ava_slider&quot;);
        var ava_width = $(&quot;ava_width&quot;);
        var ava_x = $(&quot;ava_x&quot;);
        var ava_y = $(&quot;ava_y&quot;);
        // four numbers are minx, maxx, miny, maxy
        Drag.init(handle, null, 0, 134, 0, 0);
        Drag.init(drager, avatar, -100, 350, -100, 350);
        var start_w = avatar.width;
        var start_h = avatar.height;
        var ratio = (start_h / start_w);
        var new_h;
        var new_w;
        if (ratio &amp;gt; 1) {
            new_w = 80;
            new_h = (80*start_h)/start_w;
        } else {
            new_h = 80;
            new_w = (80*start_w)/start_h;
        }
        // these need to be set after we init
        avatar.style.top = '100px';
        avatar.style.left = '100px';
        avatar.style.width = new_w + 'px';
        avatar.style.height = new_h + 'px';
        avatar.style.margin = '-' + (new_h / 2) + 'px 0 0 -' + (new_w / 2) + 'px';
        handle.style.margin = '3px 0 0 20px';
        avatar.onDrag = function(x, y) {
            ava_x.value = x;
            ava_y.value = y;
        }
        handle.onDrag = function(x, y) {
            var n_width = (new_w + (x * 2));
            var n_height = (new_h + ((x * 2) * ratio));         
            avatar.style.width = n_width + 'px';
            avatar.style.height = n_height+ 'px';
            ava_width.value = n_width;  
            avatar.style.margin = '-' + (n_height / 2) + 'px 0 0 -' + (n_width / 2) + 'px';
        }
    }
}
Event.observe(window,'load',setupAva, false);
// ]]&amp;gt;
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If this isn't exactly crystal clear, I can explain.  If you're new to &lt;a href=&quot;http://prototype.conio.net/&quot;&gt;prototype&lt;/a&gt;, &lt;code&gt;$()&lt;/code&gt; is the same as &lt;code&gt;doucment.getElementByID()&lt;/code&gt; (at least for our purposes).&lt;/p&gt;

&lt;p&gt;We need to initialize two draggable elements, one is our slider for zooming and the other is our avatar itself.  We initialize the draggers using &lt;code&gt;Drag.init()&lt;/code&gt;.  We specify what to drag, if another element should be used as a handle and then the range of motion in xy coordinates.  In the second call we use that &lt;code&gt;#dragger&lt;/code&gt; to move around the image in this manner.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Drag.init(handle, null, 0, 134, 0, 0);
Drag.init(drager, avatar, -100, 350, -100, 350);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We want to initialize the the size and placement of the avatar.  We do that using maths.  First we want it in our 80x80 pixel box.  So it should be roughly 80x80.  I've set the math up so that the &lt;em&gt;smallest&lt;/em&gt; side is 80 pixels (there's reasons for doing this the other way around).&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    if (ratio &amp;gt; 1) {
        new_w = 80;
        new_h = (80*start_h)/start_w;
    } else {
        new_h = 80;
        new_w = (80*start_w)/start_h;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We then place the avatar element.  We initialize it to be in the center of the screen (&lt;code&gt;top: 100px;left:100px&lt;/code&gt;) and then nudge the image using margins.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    avatar.style.top = '100px';
    avatar.style.left = '100px';
    avatar.style.width = new_w + 'px';
    avatar.style.height = new_h + 'px';
    avatar.style.margin = '-' + (new_h / 2) + 'px 0 0 -' + (new_w / 2) + 'px';
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We also use margins to place the handle.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    handle.style.margin = '3px 0 0 20px';
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;#ava_x&lt;/code&gt; and &lt;code&gt;#ava_y&lt;/code&gt; tell us where the center of the avatar is.  So when the avatar is moved we need to set these again:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    avatar.onDrag = function(x, y) {
        ava_x.value = x;
        ava_y.value = y;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That was easy.  Slighly more complicated is the zoomer function.  We are basically adjusting the width and the height proportionately based on roughly where the slider is.  Note that we're still using that &lt;code&gt;ratio&lt;/code&gt; variable that we calculated earlier.  We basically take the new x-coordinate of the handle and allow our image to get just slightly larger than the &lt;code&gt;#ava_image&lt;/code&gt; container.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    handle.onDrag = function(x, y) {
        var n_width = (new_w + (x * 2));
        var n_height = (new_h + ((x * 2) * ratio));         
        avatar.style.width = n_width + 'px';
        avatar.style.height = n_height+ 'px';
        ava_width.value = n_width;  
        avatar.style.margin = '-' + (n_height / 2) + 'px 0 0 -' + (n_width / 2) + 'px';
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We want to load initialize the slider right away when the page loads: &lt;code&gt;Event.observe(window,'load',setupAva, false);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Not terribly hard or complicated.  Once these elements are all in place you have a working functioning slider.  It returns the x and y coordinates of the center of the image with respect to our 200x200 pixel &lt;code&gt;#ava_image&lt;/code&gt;.  It also tells us the new width of our image.  We feed this information into a new script and out should pop a new image which matches &lt;em&gt;exactly&lt;/em&gt; what we see in our &lt;acronym title=&quot;Graphical User Interface&quot;&gt;GUI&lt;/acronym&gt;.&lt;/p&gt;

&lt;!--nextpage--&gt;


&lt;h3&gt;Processing the crop&lt;/h3&gt;

&lt;p&gt;Initially I was frustrated with the data that was being sent.  I knew the center of the image in relation to this 200x200 pixel canvas and its width... but what could I do with that.  Well I could just recreate what I saw in the &lt;acronym title=&quot;Graphical User Interface&quot;&gt;GUI&lt;/acronym&gt;.  I needed to create a 200x200 pixel image first, place my original avatar resized (and resampled) at the precise coordinates and &lt;em&gt;then&lt;/em&gt; cut out the center most 80x80 pixels to become the final avatar image.&lt;/p&gt;

&lt;p&gt;If you note in our template above for &lt;code&gt;cropSuccess.php&lt;/code&gt; we submit our form back to the &lt;code&gt;crop&lt;/code&gt; action.  Let's look at the action:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public function executeCrop()
{
    if ($this-&amp;gt;getRequestParameter('file')&amp;amp;&amp;amp;$this-&amp;gt;getRequestParameter('width')) {          // we are saving our cropped image
        // Load the original avatar into a GD image so we can manipulate it with GD
        $o_filename = $this-&amp;gt;getRequestParameter('file');  // we'll use this to find the file on our system
        $o_filename = sfConfig::get('sf_root_dir').'/web' . $o_filename;
        $o_im = @imagecreatetruecolor(80, 80) or die(&quot;Cannot Initialize new GD image stream&quot;);
        $o_imagetype = exif_imagetype($o_filename); // is this gif/jpeg/png

        // appropriately create the GD image
        switch ($o_imagetype) {
            case 1: // gif
                $o_im = imagecreatefromgif($o_filename);
                break;
            case 2: // jpeg
                $o_im = imagecreatefromjpeg($o_filename);   
                break;
            case 3: // png
                $o_im = imagecreatefrompng($o_filename);
                break;
        }

        // Let's create our canvas
        $im = @imagecreatetruecolor(200, 200) or die(&quot;Cannot Initialize new GD image stream&quot;);
        imagecolortransparent ( $im, 127 ); // set the transparency color to 127
        imagefilledrectangle( $im, 0, 0, 200, 200, 127 ); // fill the canvas with a transparent rectangle

        // let's get the new dimension for our image

        $new_width = $this-&amp;gt;getRequestParameter('width');
        $o_width = imageSX($o_im);
        $o_height = imageSY($o_im);

        $new_height = $o_height/$o_width * $new_width;

        // we place the image at the xy coordinate and then shift it so that the image is now centered at the xy coordinate
        $x = $this-&amp;gt;getRequestParameter('x') - $new_width/2;
        $y = $this-&amp;gt;getRequestParameter('y') - $new_height/2;

        // copy the original image resized and resampled onto the canvas
        imagecopyresampled($im,$o_im,$x,$y,0,0,$new_width,$new_height,$o_width,$o_height); 
        imagedestroy($o_im);

        // $final will be our final image, we will chop $im and take out the 80x80 center
        $final = @imagecreatetruecolor(80, 80) or die(&quot;Cannot Initialize new GD image stream&quot;);
        imagecolortransparent ( $final, 127 ); // maintain transparency

        //copy the center of our original image and store it here
        imagecopyresampled ( $final, $im, 0, 0, 60, 60, 80, 80, 80, 80 );
        imagedestroy($im);

        //save our new user pic
        $p = new Userpic();
        $p-&amp;gt;setUser($this-&amp;gt;getUser()-&amp;gt;getUser());
        $p-&amp;gt;setGD2($final);
        $p-&amp;gt;save();
        imagedestroy($final);
        $this-&amp;gt;userpic = $p;
        return &quot;Finished&quot;;
    }


    $this-&amp;gt;getResponse()-&amp;gt;addJavascript(&quot;dom-drag&quot;);
    $this-&amp;gt;getResponse()-&amp;gt;addJavascript('/sf/js/prototype/prototype');
    $this-&amp;gt;getResponse()-&amp;gt;addJavascript('/sf/js/prototype/effects');
    $this-&amp;gt;image = '/images/userpics/originals/' . $this-&amp;gt;getRequestParameter('file');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It's doing exactly what the paragraph above explains when the image dimensions are given.  The code is well commented so it should be easy enough to follow.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.php.net/manual/en/ref.image.php&quot;&gt;GD image functions in PHP&lt;/a&gt; are fairly robust and can help you do a lot of tricks with image data.  Note the code to save the image, we'll cover it in detail soon.&lt;/p&gt;

&lt;h3&gt;The Model&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$p = new Userpic();
$p-&amp;gt;setUser($this-&amp;gt;getUser()-&amp;gt;getUser());
$p-&amp;gt;setGD2($final);
$p-&amp;gt;save();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;First some clarification the second line.  &lt;code&gt;myUser::getUser()&lt;/code&gt; gets the &lt;code&gt;User&lt;/code&gt; object associated with the currently logged in user.  The third line, however, is where the magic happens.  Before we look at it, let's have a quick look at our model:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;userpic:
 _attributes: { phpName: Userpic }
 id:
 user_id:
 image: blob
 thumb: blob
 created_at:
 updated_at:
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We have an &lt;code&gt;image&lt;/code&gt; attribute and a &lt;code&gt;thumb&lt;/code&gt; property to our &lt;code&gt;Userpic&lt;/code&gt; object.  This is where we store &lt;acronym title=&quot;Portable Network Graphics&quot;&gt;PNG&lt;/acronym&gt; versions of each icon and their 16x16 thumbnails respectively.  We do this in &lt;code&gt;Userpic::setGD2()&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public function setGD2($gd2_image)
{
    //convert to PNG
    ob_start();
    imagepng($gd2_image);
    $png = ob_get_clean();
    //save 16x16
    $gd2_tn = @imagecreatetruecolor(16, 16) or die(&quot;Cannot Initialize new GD image stream&quot;);
    imagealphablending( $gd2_tn, true );
    imagecolortransparent ( $gd2_tn, 127 );

    imagecopyresampled ( $gd2_tn, $gd2_image, 0, 0, 0, 0, 16, 16, 80, 80 );
    ob_start();
    imagepng($gd2_tn);
    $tn = ob_get_clean();

    $this-&amp;gt;setImage($png);
    $this-&amp;gt;setThumb($tn);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We capture the output of the full size &lt;acronym title=&quot;Portable Network Graphics&quot;&gt;PNG&lt;/acronym&gt;, then we scale it again and capture the output of the thumbnail and set them.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;When it comes to web apps, having a relatively simple &lt;acronym title=&quot;Graphical User Interface&quot;&gt;GUI&lt;/acronym&gt; for people to resize images can go a long way in terms of adoption rate of avatars and custom user pictures by non technical users.&lt;/p&gt;

&lt;p&gt;Enjoy, and if you found this useful (or better implemented it) let me know.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Using Zend Search Lucene in a symfony app</title>
   <link href="http://davedash.com/2006/08/25/using-zend-search-lucene-in-a-symfony-app/"/>
   <updated>2006-08-25T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/08/25/using-zend-search-lucene-in-a-symfony-app</id>
   <content type="html">&lt;p&gt;[tags]zend, search, lucene, zend search lucene, zsl, symfony,php[/tags]&lt;/p&gt;

&lt;p&gt;If you're like me you've probably followed the &lt;a href=&quot;http://symfony-project.com/askeet/21&quot;&gt;Askeet tutorial on Search&lt;/a&gt; in order to create a decent search engine for your web app.  It's fairly straight forward, but they hinted that when &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;Zend Search Lucene&lt;/a&gt; (&lt;acronym title=&quot;Zend Search Lucene&quot;&gt;ZSL&lt;/acronym&gt;) is released, that might be the way to go.  Well we are in luck, &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;&lt;acronym title=&quot;Zend Search Lucene&quot;&gt;ZSL&lt;/acronym&gt;&lt;/a&gt; is available, so let's just dive right in.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;If you aren't using &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; have a look at &lt;a href=&quot;http://devzone.zend.com/node/view/id/91&quot; title=&quot;Roll Your Own Search Engine with Zend_Search_Lucene&quot;&gt;this article&lt;/a&gt; from the &lt;a href=&quot;http://devzone.zend.com/&quot;&gt;Zend Developer Zone&lt;/a&gt;.  It covers just enough to get you started.  If you are using &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, just follow along and we'll get you where you need to go.&lt;/p&gt;

&lt;h3&gt;Obtaining Zend Search Lucene&lt;/h3&gt;

&lt;p&gt;First &lt;a href=&quot;http://framework.zend.com/download&quot; title=&quot;Zend Framework Download&quot;&gt;download&lt;/a&gt; the &lt;a href=&quot;http://framework.zend.com/&quot;&gt;Zend Framework&lt;/a&gt; (&lt;acronym title=&quot;Zend Developer Framework&quot;&gt;ZF&lt;/acronym&gt;).  The &lt;a href=&quot;http://framework.zend.com/&quot;&gt;Zend Framework&lt;/a&gt;  is supposed to be fairly &quot;easy&quot; in terms of installation.  So let's put that to the test.  Open your &lt;a href=&quot;http://framework.zend.com/&quot;&gt;&lt;acronym title=&quot;Zend Developer Framework&quot;&gt;ZF&lt;/acronym&gt;&lt;/a&gt; archive.  Copy &lt;code&gt;Zend.php&lt;/code&gt; and &lt;code&gt;Zend/Search&lt;/code&gt; to your &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; project's library folder:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cp Zend.php $SF_PROJECT/lib              
mkdir $SF_PROJECT/lib/Zend
cp -r Zend/Search $SF_PROJECT/lib/Zend
cp Zend/Exception.php $SF_PROJECT/lib/Zend                 
chmod -R a+r $SF_PROJECT/lib/Zend*
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Index Something&lt;/h3&gt;

&lt;p&gt;We'll deviate slightly from &lt;a href=&quot;http://spindrop.us/category/reviewsbyus&quot; title=&quot;ReviewsBy.Us category of Spindrop&quot;&gt;food themed&lt;/a&gt; tutorials and do something generic.  Let's try a user search where we can find a user by their name or email address.  It's fairly simple to accomplish, and hardly requires the use of &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;&lt;acronym title=&quot;Zend Search Lucene&quot;&gt;ZSL&lt;/acronym&gt;&lt;/a&gt;, but by using &lt;acronym title=&quot;Zend Search Lucene&quot;&gt;ZSL&lt;/acronym&gt; we can easily extend it to do a full-text search of a user's profile or any other textual data.&lt;/p&gt;

&lt;p&gt;Each &quot;thing&quot; stored in the index is a &quot;document&quot; in &lt;acronym title=&quot;Zend Search Lucene&quot;&gt;ZSL&lt;/acronym&gt;, specifically a &lt;code&gt;Zend_Search_Lucene_Document&lt;/code&gt;.  Each document then consists of several &quot;fields&quot; (&lt;code&gt;Zend_Search_Lucene_Field&lt;/code&gt; objects).  In our example, our document will be an individual user and the fields will be relevant attributes of the user (username, first name, last name, email, the text of their profile).&lt;/p&gt;

&lt;p&gt;We're going to write a general re-indexing tool.  Something that will index all users.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;userActions&lt;/code&gt; class let's add the following action:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function executeReindex()
    {
        require_once 'Zend/Search/Lucene.php';
        $index = new Zend_Search_Lucene(sfConfig::get('app_search_user_index_file'),true);
        
        $users = UserPeer::doSelect(new Criteria());
        foreach ($users AS $user)
        {
            $doc = new Zend_Search_Lucene_Document();
            $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('id', $user-&gt;getId()));
            $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('username', $user-&gt;getUsername()));
            $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('email', $user-&gt;getEmail()));
            $doc-&gt;addField(Zend_Search_Lucene_Field::Text('firstname', $user-&gt;getFirstname()));
            $doc-&gt;addField(Zend_Search_Lucene_Field::Text('lastname', $user-&gt;getLastname()));
            $doc-&gt;addField(Zend_Search_Lucene_Field::Unstored('contents', &quot;{$user-&gt;getEmail()} {$user-&gt;getFirstname()} {$user-&gt;getLastname()} {$user-&gt;getUsername()}&quot;));
            $index-&gt;addDocument($doc);
        }
        
        $index-&gt;commit();
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The code should be fairly easy to follow.  First of all we're requiring the necessary libraries for Lucene.  The next line we are creating the index:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    $index = new Zend_Search_Lucene(sfConfig::get('app_search_user_index_file'),true);
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;app_search_user_index_file&lt;/code&gt; is a symfony configuration that you define in your &lt;code&gt;app.yml&lt;/code&gt;.  It defines which file you want to use for your index.  &lt;code&gt;/tmp/lucene.user.index&lt;/code&gt; works for our purposes.   The second parameter tells Lucene we are creating a new index.&lt;/p&gt;

&lt;p&gt;We then loop through all the users and for each user create a document.  For all the search relevant attributes that a user might have we add a field into the document.  Note the last field:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    $doc-&gt;addField(Zend_Search_Lucene_Field::Unstored('contents', &quot;{$user-&gt;getEmail()} {$user-&gt;getFirstname()} {$user-&gt;getLastname()} {$user-&gt;getUsername()}&quot;));
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;By default search is made for the &quot;contents&quot; field.  So in this example we want people to be able to type in someone's name, email, username without having to specify what field we're searching for.&lt;/p&gt;

&lt;h3&gt;Find those users&lt;/h3&gt;

&lt;p&gt;Finding the user's is equally as straight-forward.  We make a new action called &lt;code&gt;search&lt;/code&gt;:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function executeSearch()
    {
        require_once('Zend/Search/Lucene.php');
        $query = $this-&gt;getRequestParameter('q');
    
        $this-&gt;getResponse()-&gt;setTitle('Search for \'' . $query . '\' &amp;laquo; ' . sfConfig::get('app_title'), true);
    
        $hits = array();
    
        if ($query)
        {
            $index = new Zend_Search_Lucene(sfConfig::get('app_search_user_index_file'));
            $hits = $index-&gt;find(strtolower($query));
        }
        $this-&gt;hits = $hits;
    }

The magic happens in our `if` statement:

    if ($query)
    {
        $index = new Zend_Search_Lucene(sfConfig::get('app_search_user_index_file'));
        $hits = $index-&gt;find(strtolower($query));
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;If we have a query, open the &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;ZSL&lt;/a&gt; index (note that we only have one parameter here).  Run the &lt;code&gt;find&lt;/code&gt; method to find our query and store it to the &lt;code&gt;$hits&lt;/code&gt; array.  Note that our query was cleaned with &lt;code&gt;strtolower&lt;/code&gt;, since &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;ZSL&lt;/a&gt; is case sensitive.&lt;/p&gt;

&lt;p&gt;The template takes care of the rest:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    &lt;?php use_helper('Form');?&gt;
    &lt;?php echo form_tag('@search_users') ?&gt;
    &lt;?php echo input_tag('q'); ?&gt;
    &lt;?php echo submit_tag() ?&gt;
    &lt;/form&gt;
    &lt;?php foreach ($hits as $hit): ?&gt;
      &lt;?php echo $hit-&gt;score ?&gt;
      &lt;?php echo $hit-&gt;firstname ?&gt;
      &lt;?php echo $hit-&gt;lastname ?&gt;
      &lt;?php echo $hit-&gt;email ?&gt;
    &lt;?php endforeach ?&gt;
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Fairly simple... but it could use some cleaning up (enjoy).&lt;/p&gt;

&lt;h3&gt;What about new users?&lt;/h3&gt;

&lt;p&gt;Regularly reindexing might be nice in terms of having an optimized search index, but its lousy if you want to be able to search the network immediately when new people join on.  So why not automatically re-index each user every time they are created or everytime one of their indexed components is summoned?&lt;/p&gt;

&lt;p&gt;This should be fairly simple by adding to the &lt;code&gt;User&lt;/code&gt; class:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    var $reindex = false;
    public function setUsername ( $v )
    {
        parent::setUsername($v);
        $this-&gt;reindex = true;
    }
    public function setFirstname ( $v )
    {
        parent::setFirstname($v);
        $this-&gt;reindex = true;
    }
    public function setLastname ( $v )
    {
        parent::setLastname($v);
        $this-&gt;reindex = true;
    }
    public function setEmail ( $v )
    {
        parent::setEmail($v);
        $this-&gt;reindex = true;
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;We have an attribute called &lt;code&gt;$reindex&lt;/code&gt;.  When it is false we don't need to worry about indexes.  When something significant changes, like an update to your name or email address, then we set &lt;code&gt;$reindex&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;.  Then when we save:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function save ($con = null)
    {
        parent::save($con);
        if ($this-&gt;reindex) {
            require_once 'Zend/Search/Lucene.php';
            $index = new Zend_Search_Lucene(sfConfig::get('app_search_user_index_file'));
            // first find any references to this user and delete them
            $hits = $index-&gt;find('id:'. $this-&gt;getId());
            foreach ($hits AS $hit) {
                $index-&gt;delete($hit-&gt;id);
            }
        
            $doc = $this-&gt;generateZSLDocument();
            $index-&gt;addDocument($doc);
            $index-&gt;commit();
        }
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;We're calling a new function called &lt;code&gt;generateZSLDocument&lt;/code&gt;.  It might look familiar:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function generateZSLDocument()
    {
    
        require_once 'Zend/Search/Lucene.php';
        $doc = new Zend_Search_Lucene_Document();
        $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('id', $this-&gt;getId()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('username', $this-&gt;getUsername()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('email', $this-&gt;getEmail()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Text('firstname', $this-&gt;getFirstname()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Text('lastname', $this-&gt;getLastname()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Unstored('contents', &quot;{$this-&gt;getEmail()} {$this-&gt;getFirstname()} {$this-&gt;getLastname()} {$this-&gt;getUsername()}&quot;));
        return $doc;
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Now, whenever a user is updated, so is our index.  Additionally we can modify our reindex action:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function executeReindex()
    {
        require_once('Zend/Search/Lucene.php');
        $index = new Zend_Search_Lucene(sfConfig::get('app_search_user_index_file'),true);
        
        $users = UserPeer::doSelect(new Criteria());
        foreach ($users AS $user)
        {
            
            $index-&gt;addDocument($user-&gt;generateZSLDocument);
        }
        
        $index-&gt;commit();
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;That's a &lt;strong&gt;lot&lt;/strong&gt; easier to deal with.&lt;/p&gt;

&lt;h3&gt;...and beyond&lt;/h3&gt;

&lt;p&gt;Hope this article helps some of you jumpstart your &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; apps.  Really cool, easy to implement search is here.  We no longer have to stick with shoddy solutions like HT://Dig or spend time rolling our own full text search, as the &lt;a href=&quot;http://symfony-project.com/askeet/21&quot;&gt;symfony team diligently showed us we could&lt;/a&gt;.  But there is a lot more ground to cover.  Including optimization techniques and best practices.&lt;/p&gt;

&lt;p&gt;Let me know what you think, and if you use this in any of your apps.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Using Zend Search Lucene in a symfony app</title>
   <link href="http://davedash.com/2006/08/25/using-zend-search-lucene-in-a-symfony-app/"/>
   <updated>2006-08-25T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/08/25/using-zend-search-lucene-in-a-symfony-app</id>
   <content type="html">&lt;p&gt;[tags]zend, search, lucene, zend search lucene, zsl, symfony,php[/tags]&lt;/p&gt;

&lt;p&gt;If you're like me you've probably followed the &lt;a href=&quot;http://symfony-project.com/askeet/21&quot;&gt;Askeet tutorial on Search&lt;/a&gt; in order to create a decent search engine for your web app.  It's fairly straight forward, but they hinted that when &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;Zend Search Lucene&lt;/a&gt; (&lt;acronym title=&quot;Zend Search Lucene&quot;&gt;ZSL&lt;/acronym&gt;) is released, that might be the way to go.  Well we are in luck, &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;&lt;acronym title=&quot;Zend Search Lucene&quot;&gt;ZSL&lt;/acronym&gt;&lt;/a&gt; is available, so let's just dive right in.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;If you aren't using &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; have a look at &lt;a href=&quot;http://devzone.zend.com/node/view/id/91&quot; title=&quot;Roll Your Own Search Engine with Zend_Search_Lucene&quot;&gt;this article&lt;/a&gt; from the &lt;a href=&quot;http://devzone.zend.com/&quot;&gt;Zend Developer Zone&lt;/a&gt;.  It covers just enough to get you started.  If you are using &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, just follow along and we'll get you where you need to go.&lt;/p&gt;

&lt;h3&gt;Obtaining Zend Search Lucene&lt;/h3&gt;

&lt;p&gt;First &lt;a href=&quot;http://framework.zend.com/download&quot; title=&quot;Zend Framework Download&quot;&gt;download&lt;/a&gt; the &lt;a href=&quot;http://framework.zend.com/&quot;&gt;Zend Framework&lt;/a&gt; (&lt;acronym title=&quot;Zend Developer Framework&quot;&gt;ZF&lt;/acronym&gt;).  The &lt;a href=&quot;http://framework.zend.com/&quot;&gt;Zend Framework&lt;/a&gt;  is supposed to be fairly &quot;easy&quot; in terms of installation.  So let's put that to the test.  Open your &lt;a href=&quot;http://framework.zend.com/&quot;&gt;&lt;acronym title=&quot;Zend Developer Framework&quot;&gt;ZF&lt;/acronym&gt;&lt;/a&gt; archive.  Copy &lt;code&gt;Zend.php&lt;/code&gt; and &lt;code&gt;Zend/Search&lt;/code&gt; to your &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; project's library folder:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cp Zend.php $SF_PROJECT/lib              
mkdir $SF_PROJECT/lib/Zend
cp -r Zend/Search $SF_PROJECT/lib/Zend
cp Zend/Exception.php $SF_PROJECT/lib/Zend                 
chmod -R a+r $SF_PROJECT/lib/Zend*
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Index Something&lt;/h3&gt;

&lt;p&gt;We'll deviate slightly from &lt;a href=&quot;http://spindrop.us/category/reviewsbyus&quot; title=&quot;ReviewsBy.Us category of Spindrop&quot;&gt;food themed&lt;/a&gt; tutorials and do something generic.  Let's try a user search where we can find a user by their name or email address.  It's fairly simple to accomplish, and hardly requires the use of &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;&lt;acronym title=&quot;Zend Search Lucene&quot;&gt;ZSL&lt;/acronym&gt;&lt;/a&gt;, but by using &lt;acronym title=&quot;Zend Search Lucene&quot;&gt;ZSL&lt;/acronym&gt; we can easily extend it to do a full-text search of a user's profile or any other textual data.&lt;/p&gt;

&lt;p&gt;Each &quot;thing&quot; stored in the index is a &quot;document&quot; in &lt;acronym title=&quot;Zend Search Lucene&quot;&gt;ZSL&lt;/acronym&gt;, specifically a &lt;code&gt;Zend_Search_Lucene_Document&lt;/code&gt;.  Each document then consists of several &quot;fields&quot; (&lt;code&gt;Zend_Search_Lucene_Field&lt;/code&gt; objects).  In our example, our document will be an individual user and the fields will be relevant attributes of the user (username, first name, last name, email, the text of their profile).&lt;/p&gt;

&lt;p&gt;We're going to write a general re-indexing tool.  Something that will index all users.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;userActions&lt;/code&gt; class let's add the following action:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function executeReindex()
    {
        require_once 'Zend/Search/Lucene.php';
        $index = new Zend_Search_Lucene(sfConfig::get('app_search_user_index_file'),true);
        
        $users = UserPeer::doSelect(new Criteria());
        foreach ($users AS $user)
        {
            $doc = new Zend_Search_Lucene_Document();
            $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('id', $user-&gt;getId()));
            $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('username', $user-&gt;getUsername()));
            $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('email', $user-&gt;getEmail()));
            $doc-&gt;addField(Zend_Search_Lucene_Field::Text('firstname', $user-&gt;getFirstname()));
            $doc-&gt;addField(Zend_Search_Lucene_Field::Text('lastname', $user-&gt;getLastname()));
            $doc-&gt;addField(Zend_Search_Lucene_Field::Unstored('contents', &quot;{$user-&gt;getEmail()} {$user-&gt;getFirstname()} {$user-&gt;getLastname()} {$user-&gt;getUsername()}&quot;));
            $index-&gt;addDocument($doc);
        }
        
        $index-&gt;commit();
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;The code should be fairly easy to follow.  First of all we're requiring the necessary libraries for Lucene.  The next line we are creating the index:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    $index = new Zend_Search_Lucene(sfConfig::get('app_search_user_index_file'),true);
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;app_search_user_index_file&lt;/code&gt; is a symfony configuration that you define in your &lt;code&gt;app.yml&lt;/code&gt;.  It defines which file you want to use for your index.  &lt;code&gt;/tmp/lucene.user.index&lt;/code&gt; works for our purposes.   The second parameter tells Lucene we are creating a new index.&lt;/p&gt;

&lt;p&gt;We then loop through all the users and for each user create a document.  For all the search relevant attributes that a user might have we add a field into the document.  Note the last field:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    $doc-&gt;addField(Zend_Search_Lucene_Field::Unstored('contents', &quot;{$user-&gt;getEmail()} {$user-&gt;getFirstname()} {$user-&gt;getLastname()} {$user-&gt;getUsername()}&quot;));
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;By default search is made for the &quot;contents&quot; field.  So in this example we want people to be able to type in someone's name, email, username without having to specify what field we're searching for.&lt;/p&gt;

&lt;h3&gt;Find those users&lt;/h3&gt;

&lt;p&gt;Finding the user's is equally as straight-forward.  We make a new action called &lt;code&gt;search&lt;/code&gt;:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function executeSearch()
    {
        require_once('Zend/Search/Lucene.php');
        $query = $this-&gt;getRequestParameter('q');
    
        $this-&gt;getResponse()-&gt;setTitle('Search for \'' . $query . '\' &amp;laquo; ' . sfConfig::get('app_title'), true);
    
        $hits = array();
    
        if ($query)
        {
            $index = new Zend_Search_Lucene(sfConfig::get('app_search_user_index_file'));
            $hits = $index-&gt;find(strtolower($query));
        }
        $this-&gt;hits = $hits;
    }

The magic happens in our `if` statement:

    if ($query)
    {
        $index = new Zend_Search_Lucene(sfConfig::get('app_search_user_index_file'));
        $hits = $index-&gt;find(strtolower($query));
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;If we have a query, open the &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;ZSL&lt;/a&gt; index (note that we only have one parameter here).  Run the &lt;code&gt;find&lt;/code&gt; method to find our query and store it to the &lt;code&gt;$hits&lt;/code&gt; array.  Note that our query was cleaned with &lt;code&gt;strtolower&lt;/code&gt;, since &lt;a href=&quot;http://framework.zend.com/manual/en/zend.search.html&quot;&gt;ZSL&lt;/a&gt; is case sensitive.&lt;/p&gt;

&lt;p&gt;The template takes care of the rest:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    &lt;?php use_helper('Form');?&gt;
    &lt;?php echo form_tag('@search_users') ?&gt;
    &lt;?php echo input_tag('q'); ?&gt;
    &lt;?php echo submit_tag() ?&gt;
    &lt;/form&gt;
    &lt;?php foreach ($hits as $hit): ?&gt;
      &lt;?php echo $hit-&gt;score ?&gt;
      &lt;?php echo $hit-&gt;firstname ?&gt;
      &lt;?php echo $hit-&gt;lastname ?&gt;
      &lt;?php echo $hit-&gt;email ?&gt;
    &lt;?php endforeach ?&gt;
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Fairly simple... but it could use some cleaning up (enjoy).&lt;/p&gt;

&lt;h3&gt;What about new users?&lt;/h3&gt;

&lt;p&gt;Regularly reindexing might be nice in terms of having an optimized search index, but its lousy if you want to be able to search the network immediately when new people join on.  So why not automatically re-index each user every time they are created or everytime one of their indexed components is summoned?&lt;/p&gt;

&lt;p&gt;This should be fairly simple by adding to the &lt;code&gt;User&lt;/code&gt; class:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    var $reindex = false;
    public function setUsername ( $v )
    {
        parent::setUsername($v);
        $this-&gt;reindex = true;
    }
    public function setFirstname ( $v )
    {
        parent::setFirstname($v);
        $this-&gt;reindex = true;
    }
    public function setLastname ( $v )
    {
        parent::setLastname($v);
        $this-&gt;reindex = true;
    }
    public function setEmail ( $v )
    {
        parent::setEmail($v);
        $this-&gt;reindex = true;
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;We have an attribute called &lt;code&gt;$reindex&lt;/code&gt;.  When it is false we don't need to worry about indexes.  When something significant changes, like an update to your name or email address, then we set &lt;code&gt;$reindex&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;.  Then when we save:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function save ($con = null)
    {
        parent::save($con);
        if ($this-&gt;reindex) {
            require_once 'Zend/Search/Lucene.php';
            $index = new Zend_Search_Lucene(sfConfig::get('app_search_user_index_file'));
            // first find any references to this user and delete them
            $hits = $index-&gt;find('id:'. $this-&gt;getId());
            foreach ($hits AS $hit) {
                $index-&gt;delete($hit-&gt;id);
            }
        
            $doc = $this-&gt;generateZSLDocument();
            $index-&gt;addDocument($doc);
            $index-&gt;commit();
        }
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;We're calling a new function called &lt;code&gt;generateZSLDocument&lt;/code&gt;.  It might look familiar:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function generateZSLDocument()
    {
    
        require_once 'Zend/Search/Lucene.php';
        $doc = new Zend_Search_Lucene_Document();
        $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('id', $this-&gt;getId()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('username', $this-&gt;getUsername()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Keyword('email', $this-&gt;getEmail()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Text('firstname', $this-&gt;getFirstname()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Text('lastname', $this-&gt;getLastname()));
        $doc-&gt;addField(Zend_Search_Lucene_Field::Unstored('contents', &quot;{$this-&gt;getEmail()} {$this-&gt;getFirstname()} {$this-&gt;getLastname()} {$this-&gt;getUsername()}&quot;));
        return $doc;
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;Now, whenever a user is updated, so is our index.  Additionally we can modify our reindex action:&lt;/p&gt;

&lt;div&gt;&lt;textarea name=&quot;code&quot; class=&quot;php&quot;&gt;
    public function executeReindex()
    {
        require_once('Zend/Search/Lucene.php');
        $index = new Zend_Search_Lucene(sfConfig::get('app_search_user_index_file'),true);
        
        $users = UserPeer::doSelect(new Criteria());
        foreach ($users AS $user)
        {
            
            $index-&gt;addDocument($user-&gt;generateZSLDocument);
        }
        
        $index-&gt;commit();
    }
&lt;/textarea&gt;&lt;/div&gt;


&lt;p&gt;That's a &lt;strong&gt;lot&lt;/strong&gt; easier to deal with.&lt;/p&gt;

&lt;h3&gt;...and beyond&lt;/h3&gt;

&lt;p&gt;Hope this article helps some of you jumpstart your &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; apps.  Really cool, easy to implement search is here.  We no longer have to stick with shoddy solutions like HT://Dig or spend time rolling our own full text search, as the &lt;a href=&quot;http://symfony-project.com/askeet/21&quot;&gt;symfony team diligently showed us we could&lt;/a&gt;.  But there is a lot more ground to cover.  Including optimization techniques and best practices.&lt;/p&gt;

&lt;p&gt;Let me know what you think, and if you use this in any of your apps.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Digg-style AJAX comment editing in PHP/symfony</title>
   <link href="http://davedash.com/2006/08/13/digg-style-ajax-comment-editing-in-phpsymfony/"/>
   <updated>2006-08-13T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/08/13/digg-style-ajax-comment-editing-in-phpsymfony</id>
   <content type="html">&lt;p&gt;&quot;&lt;a href=&quot;http://digg.com/&quot;&gt;Digg&lt;/a&gt;&quot;-style anything can be pretty slick.  The &lt;acronym title=&quot;Asynchronus Java and XML&quot;&gt;AJAX&lt;/acronym&gt;-interactions on that site make it very fun to use.  It's styles have been copied everywhere, and are definitely worth copying.  The latest feature that had caught my eye was the ability to edit your comments for a set time after posting them.  Of course, it wasn't just the ability to edit comments, it was &lt;acronym title=&quot;Asynchronus Java and XML&quot;&gt;AJAX&lt;/acronym&gt; too and it has a timer.&lt;/p&gt;

&lt;p&gt;This is totally &lt;a href=&quot;http://spindrop.us/2006/07/02/comment-editing-in-reviewsbyus/&quot; title=&quot;Comment Editing in reviewsBy.us&quot;&gt;something I could use on a restaurant review site&lt;/a&gt;.  So I started on this project.  It's pretty straight forward.&lt;!--more--&gt;  For all of your posted comments you check if the owner of them is viewing them within 3 minutes of posting the commen.  3 minutes is usually enough time to notice you made a typo, but if you disagree I'll leave it to you to figure out how to adjust the code.&lt;/p&gt;

&lt;p&gt;For example, I make a comment, realize I spelled something wrong and then I can click on my comment to edit it.  Of course using &lt;acronym title=&quot;Asynchronus Java and XML&quot;&gt;AJAX&lt;/acronym&gt; means this all happens without having to reload the web page.  Therefore the edits are seemingly quick.  So let's add it to any web site.&lt;/p&gt;

&lt;h3&gt;In Place Forms&lt;/h3&gt;

&lt;p&gt;First and foremost, the ability to edit a comment means you have a form that you can use to edit and submit your changes.  But rather than deal with creating a boring un&lt;acronym title=&quot;Asynchronus Java and XML&quot;&gt;AJAX&lt;/acronym&gt;y form, we'll enlist the help of &lt;a href=&quot;http://script.aculo.us/&quot;&gt;script.aculo.us&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, each comment is rendered using the following &lt;acronym title=&quot;HypterText Markup Language&quot;&gt;HTML&lt;/acronym&gt; and PHP:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;review_block&quot; id=&quot;comment_&amp;lt;?php echo $comment-&amp;gt;getId() ?&amp;gt;&quot;&amp;gt;  
    &amp;lt;p class=&quot;author&quot;&amp;gt;&amp;lt;?php echo link_to_user($comment-&amp;gt;getUser()) ?&amp;gt; - &amp;lt;?php echo $comment-&amp;gt;getCreatedAt('%d %B %Y') ?&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;div class=&quot;review_text&quot; id=&quot;review_text_&amp;lt;?php echo $comment-&amp;gt;getId()?&amp;gt;&quot;&amp;gt;&amp;lt;?php echo $comment-&amp;gt;getHtmlNote() ?&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note that this &lt;code&gt;div&lt;/code&gt; and it's child &lt;code&gt;div&lt;/code&gt; have unique ids that we can refer back to (&lt;code&gt;comment_n&lt;/code&gt; and &lt;code&gt;review_text_n&lt;/code&gt; where &lt;code&gt;n&lt;/code&gt; is the id of the comment).  We can use this to interact with the &lt;acronym title=&quot;Document Object Model&quot;&gt;DOM&lt;/acronym&gt; via JavaScript.  What we do is for each comment, we check if it is owned by the current visitor &lt;em&gt;and&lt;/em&gt; if it's within our prescribed 3 minute window.  We can do that with some simple PHP:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php if ($comment-&amp;gt;getUser() &amp;amp;&amp;amp; $comment-&amp;gt;getUserId() == $sf_user-&amp;gt;getId() &amp;amp;&amp;amp; time() &amp;lt; 181 + $comment-&amp;gt;getCreatedAt(null) ): ?&amp;gt;
    &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
    //&amp;lt;![CDATA[
        makeEditable('&amp;lt;?php echo $comment-&amp;gt;getId() ?&amp;gt;', &quot;&amp;lt;?php echo url_for($module . '/save?id=' . $comment-&amp;gt;getId()) ?&amp;gt;&quot;, &quot;&amp;lt;?php echo url_for('restaurantnote/show?id=' . $comment-&amp;gt;getId() . '&amp;amp;mode=raw') ?&amp;gt;&quot;, &amp;lt;?php echo 181-(time() - $comment-&amp;gt;getCreatedAt(null)) ?&amp;gt;);
    //]]&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;?php endif ?&amp;gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As you can see we run the &lt;code&gt;makeEditable()&lt;/code&gt; function for each applicable comment.  As you can guess, &lt;code&gt;makeEditable()&lt;/code&gt; makes a comment editable.  For parameters it takes the comment's id (so it can refer to it in the &lt;acronym title=&quot;Document Object Model&quot;&gt;DOM&lt;/acronym&gt; and other background scripts).  It also takes as an argument the &quot;save&quot; &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt; as well as a &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt; from which it can load the raw comment.  The last argument is for the timer.&lt;/p&gt;

&lt;p&gt;Here is our function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var editor;
var pe;
makeEditable = function(id, url, textUrl, time) {
    var div = $(&quot;review_text_&quot; + id);

    pe = new PeriodicalExecuter(function() { updateTime(id); }, 1);

    Element.addClassName($('comment_' + id), 'editable');
    new Insertion.Bottom(div, '&amp;lt;div class=&quot;edit_control&quot; id=&quot;edit_control_'+id+'&quot;&amp;gt;Edit Comment (&amp;lt;span id=&quot;time_'+id+'&quot;&amp;gt;'+time+' seconds&amp;lt;/span&amp;gt;)&amp;lt;/div&amp;gt;');

    editor = new Ajax.InPlaceEditor(div, url, { externalControl: 'edit_control_'+id, rows:6, okText: 'Save', cancelText: 'Cancel', 
    loadTextURL: textUrl, onComplete: function() { makeUneditable(id) } });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It does a couple things.  It runs a &lt;a href=&quot;http://www.sergiopereira.com/articles/prototype.js.html#Reference.PeriodicalExecuter&quot; title=&quot;PeriodicalExecutor&quot;&gt;PeriodicalExecuter&lt;/a&gt; to run the &lt;code&gt;updateTime&lt;/code&gt; function which updates our countdown timer.  It adds a &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; class to our comment &lt;code&gt;div&lt;/code&gt;.  It adds a control button to edit a comment.  Lastly it uses the &lt;a href=&quot;http://script.aculo.us/&quot;&gt;script.aculo.us&lt;/a&gt; &lt;a href=&quot;http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor&quot;&gt;Ajax.InPlaceEditor&lt;/a&gt; to do most of the magic.  The hard part is done.&lt;/p&gt;

&lt;h3&gt;Periodic Execution Timer&lt;/h3&gt;

&lt;p&gt;So the &lt;code&gt;updateTime&lt;/code&gt; function is reasonably simple.  It finds the time written out in the &lt;acronym title=&quot;Document Object Model&quot;&gt;DOM&lt;/acronym&gt; and decrements it by 1 second each second.  Once it hits zero seconds it disables itself and the ability to edit the block.  Let's take a look:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;updateTime = function(id) {
  var div = $(&quot;time_&quot;+id);
  if (div) {
    var time =  parseInt(div.innerHTML) - 1;
    div.innerHTML = time;
  }
  if (time &amp;lt; 1) {
    pe.stop();
    var editLink = $('edit_control_'+id);
    if (Element.visible(editLink)) {
      makeUneditable(id);
      editLink.parentNode.removeChild(editLink);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Call backs&lt;/h3&gt;

&lt;p&gt;We'll need a few call backs for the editor to work properly.  Since many content pieces are converted from something else to &lt;acronym title=&quot;HypterText Markup Language&quot;&gt;HTML&lt;/acronym&gt; and not directly written in &lt;acronym title=&quot;HyperText Markup Language&quot;&gt;HTML&lt;/acronym&gt; we'll need a callback that will load our text.  We'll also need a callback which will save our text (and then display it).&lt;/p&gt;

&lt;h4&gt;Load Text&lt;/h4&gt;

&lt;p&gt;The first call back we can see is referenced in the &lt;code&gt;makeEditable()&lt;/code&gt; function.  In our example it's:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;url_for('restaurantnote/show?id=' . $comment-&amp;gt;getId() . '&amp;amp;mode=raw');
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Which is a &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; route to the &lt;code&gt;restaurantnote&lt;/code&gt; module and the &lt;code&gt;show&lt;/code&gt; action with an argument &lt;code&gt;mode=raw&lt;/code&gt;.  Let's take a look at this action:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public function executeShow ()
{
    $this-&amp;gt;restaurant_note = RestaurantNotePeer::retrieveByPk($this-&amp;gt;getRequestParameter('id'));
    $this-&amp;gt;forward404Unless($this-&amp;gt;restaurant_note instanceof RestaurantNote);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;All this does is load the text (in our case the [markdown] formatting) into a template.&lt;/p&gt;

&lt;h4&gt;Save Text&lt;/h4&gt;

&lt;p&gt;The save text url in our example is:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;url_for('restaurantnote/save?id=' . $comment-&amp;gt;getId());
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Using the &lt;a href=&quot;http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor&quot;&gt;Ajax.InPlaceEditor&lt;/a&gt; the value of the text-area is saved to the &lt;code&gt;value&lt;/code&gt; POST variable.  We consume it in our action like so:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public function executeSave() 
{
    $note = RestaurantNotePeer::retrieveByPk($this-&amp;gt;getRequestParameter('id'));
    $this-&amp;gt;forward404Unless($note instanceof RestaurantNote);
    if ($note-&amp;gt;getUserId() == $this-&amp;gt;getUser()-&amp;gt;getId()) {
        $note-&amp;gt;setNote($this-&amp;gt;getRequestParameter('value'));
        $note-&amp;gt;save();
    }
    $this-&amp;gt;note = $note;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The note is also sent to a template that renders it, so when the save takes place, the edit form will be replaced with the new text.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;As you can see with some &lt;a href=&quot;http://script.aculo.us/&quot;&gt;script.aculo.us&lt;/a&gt; and &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, it's fairly easy to mimic &quot;Digg-style&quot; in-place comment editing.  You can test out a real example by visiting &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How Object-Relational Mapping saves time and makes your code sexy</title>
   <link href="http://davedash.com/2006/08/07/how-object-relational-mapping-saves-time-and-makes-your-code-sexy/"/>
   <updated>2006-08-07T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/08/07/how-object-relational-mapping-saves-time-and-makes-your-code-sexy</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Object-relational_mapping&quot; title=&quot;Object Relational Mapping on Wikipedia&quot;&gt;Object Relational mapping&lt;/a&gt; is a way of transparently interacting with a relational database by using objects.  Each database table is a new class and each row in the table is a single object.  Relations between tables are now relations between classes.&lt;/p&gt;

&lt;p&gt;It wasn't until I started using &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; and &lt;a href=&quot;http://propel.phpdb.org/&quot;&gt;propel&lt;/a&gt; that I started appreciating &lt;a href=&quot;http://en.wikipedia.org/wiki/Object-relational_mapping&quot; title=&quot;Object Relational Mapping on Wikipedia&quot;&gt;ORM&lt;/a&gt;.  I started working on significant projects and the time it would take me to do things went down quite a bit.  Prior to &lt;a href=&quot;http://propel.phpdb.org/&quot;&gt;Propel&lt;/a&gt;, I had a lot of library files that would store and retrieve information for me.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class lib {
    function valid_user($username, $pw)
    {
        $q = &quot;SELECT id FROM user WHERE username LIKE '$username' AND password LIKE '$pw'&quot;;
        return DB::do_query_select_one($q);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Not too bad, but a lot is buried in my hypothetical &lt;code&gt;do_query_select_one&lt;/code&gt; function.  Let's compare this to the &lt;acronym title=&quot;Object Relational Mapping&quot;&gt;ORM&lt;/acronym&gt; (&lt;a href=&quot;http://propel.phpdb.org/&quot;&gt;propel&lt;/a&gt;) version:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class myTools {
    public static valid_user($username, $pw)
    {
        $c = new Criteria();
        $c-&amp;gt;add(UserPeer::USERNAME, $username);
        $c-&amp;gt;add(UserPeer::PASSWORD, $pw);
        return UserPeer::doSelectOne($c);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That's a lot of extra writing, and as someone who's quite proficient in &lt;acronym title=&quot;Structured Query Language&quot;&gt;SQL&lt;/acronym&gt;, you can see why I initially laughed it off.  &lt;!--more--&gt; Let's take it a step further.  Sure we have twice as many lines of code, but what would the calling functions do after they check to see a user is valid or not?&lt;/p&gt;

&lt;h3&gt;Related Objects&lt;/h3&gt;

&lt;p&gt;In our non-&lt;acronym title=&quot;Object Relational Mapping&quot;&gt;ORM&lt;/acronym&gt; world we would attempt to iterate through each row. find some corresponding &lt;acronym title=&quot;Access Control List&quot;&gt;ACL&lt;/acronym&gt; table and add all these elements to a session variable.  This can get old fast.  Let's see how that would look:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if ($user = valid_user($_POST['username'], $_POST['pw'])) {
    // $user we populated from our made-up 
    // DB::do_query_select_one function.  Let's pretend that's easy.
    $id = $user['id'];
    $sql = 'SELECT group FROM acl WHERE user_id = ?';
    $ps = prepare_statement($sql, $id);
    // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That's neat, but in the &lt;acronym title=&quot;Object Relational Mapping&quot;&gt;ORM&lt;/acronym&gt; world we do it like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if($user = valid_user($_POST['username'], $_POST['pw']))
{
    $user-&amp;gt;getACLs();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;All the extra database calls are safely encapsulated in our class.  No worries.  It's just a one-liner.&lt;/p&gt;

&lt;h3&gt;Putting things into functions&lt;/h3&gt;

&lt;p&gt;Another neat trick is putting some redundant code into simple functions.  By using a criteria object, you can cleanly create some functions that take an input criteria and return one with specific parameters:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;function securify(Criteria $c)
{
    // makes sure the user is still valid
    $c-&amp;gt;add(User::EXPIRES, time(), CRITERIA::GREATER_EQUAL);
    $c-&amp;gt;add(User::VALID, true);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now all we need to do every time we call a user is call &lt;code&gt;securify&lt;/code&gt; on the &lt;code&gt;Criteria&lt;/code&gt; object to make sure we have a valid user that hasn't expired.&lt;/p&gt;

&lt;h3&gt;Deleting objects&lt;/h3&gt;

&lt;p&gt;Getting rid of data: &lt;code&gt;$user-&amp;gt;delete()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Customizations are saved&lt;/h3&gt;

&lt;p&gt;Let's say you want the &lt;code&gt;User&lt;/code&gt; object to have some customizations.  Any of those customizations will persist even after you change the model, since &lt;code&gt;User&lt;/code&gt; inherits from a &lt;code&gt;BaseUser&lt;/code&gt; class which is dynamically generated from a defined schema.  This can save a &lt;em&gt;ton&lt;/em&gt; of time when your model changes.  Instead of finding every instance of a call to see if a user is logged in, you can change your custom classes and not have to worry.  If this had been the case for me, I'd have saved myself and my client a few hours of coding.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;&lt;acronym title=&quot;Object Relational Mapping&quot;&gt;ORM&lt;/acronym&gt; relegates the database to simply being a store for persistent objects.  What this means is you no longer need to rely on half-baked &lt;acronym title=&quot;Structured Query Language&quot;&gt;SQL&lt;/acronym&gt; queries to save and load objects.  You can let the objects take care of that themselves, without worrying about the database back-end.  This allows you, the programmer, to do your job of manipulating objects to execute the goal of a web site.  Enjoy.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How Object-Relational Mapping saves time and makes your code sexy</title>
   <link href="http://davedash.com/2006/08/07/how-object-relational-mapping-saves-time-and-makes-your-code-sexy/"/>
   <updated>2006-08-07T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/08/07/how-object-relational-mapping-saves-time-and-makes-your-code-sexy</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Object-relational_mapping&quot; title=&quot;Object Relational Mapping on Wikipedia&quot;&gt;Object Relational mapping&lt;/a&gt; is a way of transparently interacting with a relational database by using objects.  Each database table is a new class and each row in the table is a single object.  Relations between tables are now relations between classes.&lt;/p&gt;

&lt;p&gt;It wasn't until I started using &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; and &lt;a href=&quot;http://propel.phpdb.org/&quot;&gt;propel&lt;/a&gt; that I started appreciating &lt;a href=&quot;http://en.wikipedia.org/wiki/Object-relational_mapping&quot; title=&quot;Object Relational Mapping on Wikipedia&quot;&gt;ORM&lt;/a&gt;.  I started working on significant projects and the time it would take me to do things went down quite a bit.  Prior to &lt;a href=&quot;http://propel.phpdb.org/&quot;&gt;Propel&lt;/a&gt;, I had a lot of library files that would store and retrieve information for me.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class lib {
    function valid_user($username, $pw)
    {
        $q = &quot;SELECT id FROM user WHERE username LIKE '$username' AND password LIKE '$pw'&quot;;
        return DB::do_query_select_one($q);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Not too bad, but a lot is buried in my hypothetical &lt;code&gt;do_query_select_one&lt;/code&gt; function.  Let's compare this to the &lt;acronym title=&quot;Object Relational Mapping&quot;&gt;ORM&lt;/acronym&gt; (&lt;a href=&quot;http://propel.phpdb.org/&quot;&gt;propel&lt;/a&gt;) version:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class myTools {
    public static valid_user($username, $pw)
    {
        $c = new Criteria();
        $c-&amp;gt;add(UserPeer::USERNAME, $username);
        $c-&amp;gt;add(UserPeer::PASSWORD, $pw);
        return UserPeer::doSelectOne($c);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That's a lot of extra writing, and as someone who's quite proficient in &lt;acronym title=&quot;Structured Query Language&quot;&gt;SQL&lt;/acronym&gt;, you can see why I initially laughed it off.  &lt;!--more--&gt; Let's take it a step further.  Sure we have twice as many lines of code, but what would the calling functions do after they check to see a user is valid or not?&lt;/p&gt;

&lt;h3&gt;Related Objects&lt;/h3&gt;

&lt;p&gt;In our non-&lt;acronym title=&quot;Object Relational Mapping&quot;&gt;ORM&lt;/acronym&gt; world we would attempt to iterate through each row. find some corresponding &lt;acronym title=&quot;Access Control List&quot;&gt;ACL&lt;/acronym&gt; table and add all these elements to a session variable.  This can get old fast.  Let's see how that would look:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if ($user = valid_user($_POST['username'], $_POST['pw'])) {
    // $user we populated from our made-up 
    // DB::do_query_select_one function.  Let's pretend that's easy.
    $id = $user['id'];
    $sql = 'SELECT group FROM acl WHERE user_id = ?';
    $ps = prepare_statement($sql, $id);
    // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That's neat, but in the &lt;acronym title=&quot;Object Relational Mapping&quot;&gt;ORM&lt;/acronym&gt; world we do it like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if($user = valid_user($_POST['username'], $_POST['pw']))
{
    $user-&amp;gt;getACLs();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;All the extra database calls are safely encapsulated in our class.  No worries.  It's just a one-liner.&lt;/p&gt;

&lt;h3&gt;Putting things into functions&lt;/h3&gt;

&lt;p&gt;Another neat trick is putting some redundant code into simple functions.  By using a criteria object, you can cleanly create some functions that take an input criteria and return one with specific parameters:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;function securify(Criteria $c)
{
    // makes sure the user is still valid
    $c-&amp;gt;add(User::EXPIRES, time(), CRITERIA::GREATER_EQUAL);
    $c-&amp;gt;add(User::VALID, true);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now all we need to do every time we call a user is call &lt;code&gt;securify&lt;/code&gt; on the &lt;code&gt;Criteria&lt;/code&gt; object to make sure we have a valid user that hasn't expired.&lt;/p&gt;

&lt;h3&gt;Deleting objects&lt;/h3&gt;

&lt;p&gt;Getting rid of data: &lt;code&gt;$user-&amp;gt;delete()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Customizations are saved&lt;/h3&gt;

&lt;p&gt;Let's say you want the &lt;code&gt;User&lt;/code&gt; object to have some customizations.  Any of those customizations will persist even after you change the model, since &lt;code&gt;User&lt;/code&gt; inherits from a &lt;code&gt;BaseUser&lt;/code&gt; class which is dynamically generated from a defined schema.  This can save a &lt;em&gt;ton&lt;/em&gt; of time when your model changes.  Instead of finding every instance of a call to see if a user is logged in, you can change your custom classes and not have to worry.  If this had been the case for me, I'd have saved myself and my client a few hours of coding.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;&lt;acronym title=&quot;Object Relational Mapping&quot;&gt;ORM&lt;/acronym&gt; relegates the database to simply being a store for persistent objects.  What this means is you no longer need to rely on half-baked &lt;acronym title=&quot;Structured Query Language&quot;&gt;SQL&lt;/acronym&gt; queries to save and load objects.  You can let the objects take care of that themselves, without worrying about the database back-end.  This allows you, the programmer, to do your job of manipulating objects to execute the goal of a web site.  Enjoy.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Usability for Nerds: Traversing URLs</title>
   <link href="http://davedash.com/2006/07/30/usability-for-nerds-traversing-urls/"/>
   <updated>2006-07-30T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/07/30/usability-for-nerds-traversing-urls</id>
   <content type="html">&lt;p&gt;A recent peeve of mine is &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt;s that you can't manually traverse.  Let me explain.  Let's say you visit &lt;code&gt;http://reviewsby.us/restaurant/cheesecake-factory&lt;/code&gt;.  You should manually be able to remove &lt;code&gt;cheesecake-factory&lt;/code&gt; and see an index page of restaurants at &lt;code&gt;http://reviewsby.us/restaurant/&lt;/code&gt;.  It makes logical sense for that page to be something that would enable you to find more restaurants.&lt;/p&gt;

&lt;p&gt;This is a throwback to static web sites, that consisted of directories and files.  If you accessed a file by its name, you would see the contents of the file (possibly filtered by the server).  If you accessed a directory, you would see an index of files.  In the world of web apps, however, &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt;s are made up.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;Web apps where these gaps are missing can be especially frustrating when you use your browser's history.  Often I reference the &lt;a href=&quot;http://www.symfony-project.com/api/symfony.html&quot;&gt;symfony api&lt;/a&gt;.  The &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt; is &lt;code&gt;http://www.symfony-project.com/api/symfony.html&lt;/code&gt;.  All the other &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt;s in the &lt;acronym title=&quot;Application Programing Interface&quot;&gt;API&lt;/acronym&gt; listing begin with &lt;code&gt;http://www.symfony-project.com/api/&lt;/code&gt;, so you could assume that &lt;code&gt;http://www.symfony-project.com/api/&lt;/code&gt; is an index page.&lt;/p&gt;

&lt;div style=&quot;&quot;&gt;
&lt;img src=&quot;http://static.flickr.com/60/200898792_9de3c68eb1.jpg&quot; width=&quot;500&quot; height=&quot;101&quot; alt=&quot;URL autocomplete&quot; /&gt;
&lt;/div&gt;


&lt;p&gt;It's not the index page (&lt;code&gt;http://symfony-project.com/api/symfony.html&lt;/code&gt; is).  If you googled for &lt;code&gt;sfConfig&lt;/code&gt; and got to &lt;code&gt;http://symfony-project.com/api/symfony/config/sfConfig.html&lt;/code&gt; and didn't feel like figuring out the navigation structure... or let's say at a later date you're using your browser's &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt; auto-complete feature, you will get a 404 Error&lt;a href=&quot;#fn1&quot;&gt;1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Web applications can try to mirror directory indexes with pretty &lt;acronym title=&quot;URL&quot;&gt;URL&lt;/acronym&gt;s, but often have a few gaps as every &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt; (or level of &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt;) needs to be designated in the app.  Its a good idea to fill those in as it is another way to navigate a web site.&lt;/p&gt;

&lt;div id=&quot;footnotes&quot;&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
        &lt;li id=&quot;fn1&quot;&gt;Not to pick on the wonderful symfony development team, but I truly do see this a lot on their site.  I'm sure they'll setup a redirect or something to fix this.  Or probably call me out on the fact that many of my sites violate this principle. &lt;a href=&quot;#fnr1&quot; class=&quot;footnoteBackLink&quot;  title=&quot;Jump back to footnote 1 in the text.&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>How to remove file extensions from URLs</title>
   <link href="http://davedash.com/2006/07/26/how-to-remove-file-extensions-from-urls/"/>
   <updated>2006-07-26T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/07/26/how-to-remove-file-extensions-from-urls</id>
   <content type="html">&lt;p&gt;&lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt;s should be treated as prime real estate.  Their primary purpose is to locate resources on the Internet.  So for a web developer, it makes sense to make things as user-friendly as possible.  One effort is to remove the extensions from files.  I don't mean things like &lt;code&gt;.html&lt;/code&gt; or &lt;code&gt;.pdf&lt;/code&gt;, as those give you an idea that you're reading a page of content or a &lt;acronym title=&quot;Portable Document Format&quot;&gt;PDF&lt;/acronym&gt; document.  I meant things like &lt;code&gt;.php&lt;/code&gt; or &lt;code&gt;.asp&lt;/code&gt; or &lt;code&gt;.pl&lt;/code&gt;, etc.  These are unnecessary items that just clutter the location bar on most browsers.&lt;/p&gt;

&lt;p&gt;There are two ways to do this.  The easy way which just looks at a request, if the requested filename doesn't exist, then it looks for the filename with a &lt;code&gt;.php&lt;/code&gt; (or &lt;code&gt;.asp&lt;/code&gt; or whatever) extension.  In an &lt;code&gt;.htaccess&lt;/code&gt; file:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ $1.php [L,QSA]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now if you go to: &lt;code&gt;http://domain/about&lt;/code&gt; the server will interpret it as if you went to &lt;code&gt;http://domain/about.php&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Makes sense, but if we're already breaking the relation between &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt; and filename, we may as well break it intelligently.  &lt;!--next page--&gt;Change that &lt;code&gt;.htaccess&lt;/code&gt; file:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now if you go to: &lt;code&gt;http://domain/about&lt;/code&gt; the server will interpret it as if you went to &lt;code&gt;http://domain/index.php?q=about&lt;/code&gt;.  How is this useful?  Well now &lt;code&gt;index.php&lt;/code&gt; is &lt;em&gt;always&lt;/em&gt; called, so it can do anything common to all pages (which might be nothing) and do something based on the &lt;code&gt;$_GET['q']&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;require_once('html_functions.php');
switch ($_GET['q']) {
    case 'about':
        echo myhead('about page');
        break;
    default:
        echo myhead('home page')
        break;
}
include($_GET['q'] . '.php');
echo myfoot();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We're loading a hypothetical library &lt;code&gt;html_functions.php&lt;/code&gt; which contains some simple functions (&lt;code&gt;myhead()&lt;/code&gt; and &lt;code&gt;myfoot()&lt;/code&gt;) that print out a simple header or footer for this site.  The &lt;code&gt;switch&lt;/code&gt; statement dynamically sets the &lt;code&gt;&amp;lt;head/&amp;gt;&lt;/code&gt;.  After the &lt;code&gt;switch&lt;/code&gt; we include a file based on the query string.  In our case it will still pull up &lt;code&gt;about.php&lt;/code&gt;.  Granted, this is not what I use personally, but it's the general idea behind how &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt; works.&lt;/p&gt;

&lt;h3&gt;Why?&lt;/h3&gt;

&lt;p&gt;So why go through all this nonsense?  Extensions for the most part don't mean much to an end user.  Sure, &lt;code&gt;jpg&lt;/code&gt;, &lt;code&gt;png&lt;/code&gt; or &lt;code&gt;gif&lt;/code&gt; mean images and &lt;code&gt;html&lt;/code&gt; mean web page and &lt;code&gt;pdf&lt;/code&gt; means the file is a &lt;acronym title=&quot;Portable Document Format&quot;&gt;PDF&lt;/acronym&gt; document.  Dynamic pages, however usually come from &lt;code&gt;cgi&lt;/code&gt;, &lt;code&gt;php&lt;/code&gt;, &lt;code&gt;pl&lt;/code&gt; , &lt;code&gt;asp&lt;/code&gt;  pages or some other 3 or 4 letter extension that the server uses as a hint to determine how to parse, but the output is usually &lt;code&gt;html&lt;/code&gt;.  Servers are smart though.  They don't need hints, and the above code eliminates the need to reveal so explicitly just how a page is delivered.  Take our &lt;a href=&quot;http://reviewsby.us/&quot;&gt;restaurant review site&lt;/a&gt;, for the most part you can't tell that it's done in &lt;code&gt;php&lt;/code&gt;.  In fact all the &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt;s are &quot;clean&quot; and somewhat logical.  The benefit of having clean simple &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt;s is if we decide to change from PHP to &lt;acronym title=&quot;Active Server Pages&quot;&gt;ASP&lt;/acronym&gt; for example, we won't need to change our &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt;s.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Syncing with symfony and clearing your cache in one shot</title>
   <link href="http://davedash.com/2006/07/17/syncing-with-symfony-and-clearing-your-cache-in-one-shot/"/>
   <updated>2006-07-17T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/07/17/syncing-with-symfony-and-clearing-your-cache-in-one-shot</id>
   <content type="html">&lt;p&gt;If you're using &lt;code&gt;symfony&lt;/code&gt;'s &lt;code&gt;sync&lt;/code&gt; command to synchronize files across environments (e.g. moving your development files to a staging server), it helps usually to clear the cache of the receiving server.&lt;/p&gt;

&lt;p&gt;The following line will help:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;symfony sync production go ; ssh user@production &quot;cd /var/www; symfony cc&quot;   
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Assuming you have &lt;acronym title=&quot;Secure SHell&quot;&gt;SSH&lt;/acronym&gt; keys defined and that you change &lt;code&gt;user@production&lt;/code&gt; to your username and server host as well as &lt;code&gt;/var/www&lt;/code&gt; switched to your website path.  Also the &lt;code&gt;symfony&lt;/code&gt; command needs to work on your &quot;production&quot; server (or whatever environment).&lt;/p&gt;

&lt;p&gt;There may be a cleaner way to take care of this by changing the &lt;code&gt;symfony&lt;/code&gt; command, but this works sufficiently well.&lt;/p&gt;

&lt;!--next page--&gt;


&lt;p&gt;One of the more frustrating elements of web development is synchronizing multiple sites: usually a development site, a staging site and a production site.  &lt;acronym title=&quot;SubVersioN&quot;&gt;SVN&lt;/acronym&gt; helps with keeping your code versioned, but usually you don't want to check out a copy of your web site onto your live server.&lt;/p&gt;

&lt;p&gt;Usually we use &lt;acronym title=&quot;Secure File Transfer Protocol&quot;&gt;SFTP&lt;/acronym&gt; or &lt;code&gt;rsync&lt;/code&gt;.  The former has lots of problems, because a lot of manual work is usually involved to make sure you don't over-write important files.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rsync&lt;/code&gt;, however, is a champ and &lt;a href=&quot;http://symfony-project.com/&quot; title=&quot;The symfony Project&quot;&gt;symfony&lt;/a&gt; takes advantage of this.  The key files you'll have to deal with are &lt;code&gt;$PROJECT_HOME/config/properties.ini&lt;/code&gt; and &lt;code&gt;$PROJECT_HOME/config/rsync_exclude.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Your &lt;code&gt;properties.ini&lt;/code&gt; should look roughly like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[symfony]
  name=reviewsby.us
[staging]
  host=staging.reviewsby.us
  port=22
  user=root
  dir=/var/www/staging/
[staging2]
  host=staging2.reviewsby.us
  port=22
  user=root
  dir=/var/www/staging
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Each heading other than &quot;&lt;code&gt;[symfony]&lt;/code&gt;&quot; is a different environment.  In our example, we have two staging environments.  The values under each heading should be self-explanatory.  We can now run the following commands:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;symfony sync staging
symfony sync staging go
symfony sync staging2
symfony sync staging2 go
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The commands that lack &lt;code&gt;go&lt;/code&gt; are &quot;dry-runs&quot; which just show you what files will be transfered.  The other commands will run &lt;code&gt;rsync&lt;/code&gt; and transfer all the files not specified in the exclude file, &lt;code&gt;rsync_exclude.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;See &lt;a href=&quot;http://www.symfony-project.com/askeet/22&quot;&gt;askeet&lt;/a&gt; or the &lt;a href=&quot;http://www.symfony-project.com/content/book/page/deployment.html&quot;&gt;symfony documentation&lt;/a&gt; for more details.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Dynamic Linking to Syndication Feeds with symfony</title>
   <link href="http://davedash.com/2006/07/04/dynamic-linking-to-syndication-feeds-with-symfony/"/>
   <updated>2006-07-04T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/07/04/dynamic-linking-to-syndication-feeds-with-symfony</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://www.symfony-project.com/content/book/page/syndication.html&quot; title=&quot;How to build a syndication feed&quot;&gt;Adding a statically-linked syndication feed&lt;/a&gt;, a feed that is the same no matter where on the site you are, is a cinch with &lt;a href=&quot;http://symfony-project.com/&quot;&gt;symfony&lt;/a&gt;, but what about dynamically linked syndication feeds?  Let's say we're building the &lt;a href=&quot;http://reviewsby.us/&quot;&gt;latest and greatest Web 2.0 app&lt;/a&gt;, there's going to be hundreds of &lt;acronym title=&quot;Really Simple Syndication&quot;&gt;RSS&lt;/acronym&gt; feeds, not just the most recent items.  We'll want the latest comments to a post, the favorite things of a website member and it all has to be feed enabled.  Sure, we can slap a link to the &lt;acronym title=&quot;Real Simple Syndication&quot;&gt;RSS&lt;/acronym&gt; feed and call it a day, but let's go a step further and stick it in the &lt;code&gt;&amp;lt;head/&amp;gt;&lt;/code&gt; area as well.  That way when someone clicks on the &lt;acronym title=&quot;Real Simple Syndication&quot;&gt;RSS&lt;/acronym&gt; icon in their browser, or adds a web page to &lt;a href=&quot;http://bloglines.com&quot;&gt;Bloglines&lt;/a&gt; those extra feeds can be found.&lt;/p&gt;

&lt;!--break--&gt;


&lt;h2&gt;Expanding your head&lt;/h2&gt;

&lt;p&gt;A typical &lt;code&gt;layout.php&lt;/code&gt; for a symfony app will have a &lt;code&gt;&amp;lt;head/&amp;gt;&lt;/code&gt; section like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;head&amp;gt;
    &amp;lt;?php echo include_http_metas() ?&amp;gt;
    &amp;lt;?php echo include_metas() ?&amp;gt;
    &amp;lt;?php echo include_title() ?&amp;gt;
    &amp;lt;?php echo auto_discovery_link_tag('rss', 'feed/latest')?&amp;gt;  
    &amp;lt;?php echo auto_discovery_link_tag('rss', '@feed_latest_georss', 
    array('title' =&amp;gt; 'Latest Restaurants\' Locations (GeoRSS)' ))?&amp;gt;     
    &amp;lt;?php echo include_feeds() ?&amp;gt;&amp;lt;!-- this is the custom feed includer --&amp;gt;
    &amp;lt;link rel=&quot;shortcut icon&quot; href=&quot;/favicon.ico&quot; /&amp;gt;
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Since this is in the &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; &lt;code&gt;layout.php&lt;/code&gt;, the latest feed and the latest GeoRSS feed (which we developed in &lt;a href=&quot;http://spindrop.us/2006/04/26/easy_yahoo_maps_and_georss_with_symfony&quot; title=&quot;Easy Yahoo Maps! with symfony&quot;&gt;this article&lt;/a&gt;) will show up on every page.  So for example, if you use &lt;a href=&quot;http://www.mozilla.com/firefox/&quot; title=&quot;Firefox&quot;&gt;FireFox&lt;/a&gt;, you can subscribe to either link when you click on the orange feed icon (&lt;img src=&quot;http://feedicons.com/images/layout/feed-icon-12x12.gif&quot; title=&quot;Feed Available&quot; alt=&quot;feed&quot; /&gt;) in the &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt; bar no matter where you are in the web-application.&lt;/p&gt;

&lt;p&gt;To expand this to allow for multiple feeds, we need to include &lt;code&gt;&amp;lt;?php echo include_feeds() ?&amp;gt;&lt;/code&gt; (before or after the &lt;code&gt;auto_discovery_link_tag&lt;/code&gt; calls makes the most sense).&lt;/p&gt;

&lt;h2&gt;Making the Feed Helper&lt;/h2&gt;

&lt;p&gt;Let's created a &lt;code&gt;FeedHelper.php&lt;/code&gt; to put the &lt;code&gt;include_feeds()&lt;/code&gt; function (don't forget to add &lt;code&gt;use_helper('Feed')&lt;/code&gt; to your &lt;code&gt;layout.php&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The function looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;function include_feeds()
{
    $type = 'rss';
    $already_seen = array();
    foreach (sfContext::getInstance()-&amp;gt;getRequest()-&amp;gt;getAttributeHolder()-&amp;gt;getAll('helper/asset/auto/feed') as $files)
    {
        if (!is_array($files))
        {
            $files = array($files);
        }
        foreach ($files as $file)
        {
            if (isset($already_seen[$file])) continue;
            $already_seen[$file] = 1;
            echo tag('link', array('rel' =&amp;gt; 'alternate', 'type' =&amp;gt; 'application/'.$type.'+xml', 'title' =&amp;gt; ucfirst($type), 'href' =&amp;gt; url_for($file, true)));
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The function is doing what the deprecated &lt;code&gt;include_javascripts&lt;/code&gt; and &lt;code&gt;include_stylesheets&lt;/code&gt; functions did, just with syndication feeds.  Also note, I stuck to just using &lt;acronym title=&quot;Really Simple Syndication&quot;&gt;RSS&lt;/acronym&gt; feeds.  This function can no doubt be extended to Atom or other feed types, but for my purposes it was unnecessary&lt;sup id=&quot;fnr1&quot;&gt;&lt;a href=&quot;#fn1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;h2&gt;Dynamically setting the feeds&lt;/h2&gt;

&lt;p&gt;In the &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; site, the menu items are tagged.  There's tags for &lt;a href=&quot;http://reviewsby.us/tag/chicken&quot;&gt;chicken&lt;/a&gt;, &lt;a href=&quot;http://reviewsby.us/tag/indian&quot;&gt;indian&lt;/a&gt; and &lt;a href=&quot;http://reviewsby.us/tag/bread&quot;&gt;bread&lt;/a&gt; for example.  Each of them are to have an associated GeoRSS feed as described in a &lt;a href=&quot;http://spindrop.us/2006/04/26/easy_yahoo_maps_and_georss_with_symfony&quot; title=&quot;Easy Yahoo Maps! with symfony&quot;&gt;previous tutorial&lt;/a&gt;.  I built our tagging system similar to &lt;a href=&quot;http://symfony-project.com/askeet&quot;&gt;Askeet&lt;/a&gt;.  So in our &lt;code&gt;tag&lt;/code&gt; module I created a function in the corresponding &lt;code&gt;actions.class.php&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public function addFeed($feed)
{
    $this-&amp;gt;getRequest()-&amp;gt;setAttribute($feed, $feed, 'helper/asset/auto/feed');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This sets the attribute that &lt;code&gt;include_feeds()&lt;/code&gt; pulls from.  Here &lt;code&gt;$feed&lt;/code&gt; is simply the route to our feed.  So in our &lt;code&gt;executeShow()&lt;/code&gt; I just make a call to &lt;code&gt;$this-&amp;gt;addFeed('@feed_tag_georss?tag=' . $tag)&lt;/code&gt;.  We're done.&lt;/p&gt;

&lt;p&gt;We can now go to any of our tagged pages.  Let's try &lt;a href=&quot;http://reviewsby.us/tag/chicken&quot;&gt;chicken&lt;/a&gt; and see that we can subscribe to a &lt;a href=&quot;http://reviewsby.us/tag/chicken/geo.rss&quot;&gt;GeoRSS feed&lt;/a&gt; of restaurants serving &lt;a href=&quot;http://reviewsby.us/tag/chicken&quot;&gt;dishes tagged as chicken&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Slight problem.  The &lt;code&gt;title&lt;/code&gt; attribute of the generated &lt;code&gt;link&lt;/code&gt; tags are always &lt;code&gt;Rss&lt;/code&gt;.  That can be mildly unusable.&lt;/p&gt;

&lt;h2&gt;Throwing feed titles into the mix&lt;/h2&gt;

&lt;p&gt;Let's change our &lt;code&gt;addFeed()&lt;/code&gt; to allow for a second parameter, a title and have it store both the route and the title in the request attribute:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public function addFeed($feed, $title = null)
{
    $feedArray = array('url' =&amp;gt; $feed, 'title' =&amp;gt; $title);
    $this-&amp;gt;getRequest()-&amp;gt;setAttribute($feed, $feedArray, 'helper/asset/auto/feed');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We'll also need to adapt the &lt;code&gt;include_feeds&lt;/code&gt; to appropriately accommodate associative arrays:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;function include_feeds()
{
    $type = 'rss';
    $already_seen = array();
    foreach (sfContext::getInstance()-&amp;gt;getRequest()-&amp;gt;getAttributeHolder()-&amp;gt;getAll('helper/asset/auto/feed') as $feeds)
    {
        if (!is_array($feeds) || is_associative($feeds))
        {
            $feeds = array($feeds);
        }

        foreach ($feeds as $feed)
        {
            if (is_array($feed)) {
                $file = $feed['url'];
                $title = empty($feed['title']) ? $type : $feed['title'];
            } else {
                $file = $feed;
                $title = $type;
            }

            if (isset($already_seen[$file])) continue;

            $already_seen[$file] = 1;
            echo tag('link', array('rel' =&amp;gt; 'alternate', 'type' =&amp;gt; 'application/'.$type.'+xml', 'title' =&amp;gt; $title, 'href' =&amp;gt; url_for($file, true)));
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note, there's a function &lt;code&gt;is_associative()&lt;/code&gt;.  It's a custom function that we can place in another helper:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;function is_associative($array)
{
  if (!is_array($array) || empty($array)) return false;
  $keys = array_keys($array);
  return array_keys($keys) !== $keys;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It's a clever way of determining if a function is an associative array or not.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;It looks like our GeoRSS feeds are on all our tag pages.  Now we can take our favorite items labeled as &lt;a href=&quot;http://reviewsby.us/tag/indian&quot;&gt;Indian food&lt;/a&gt; and easily add the &lt;acronym title=&quot;Universal Resource Locator&quot;&gt;URL&lt;/acronym&gt; to a service like &lt;a href=&quot;http://bloglines.com/&quot;&gt;Bloglines&lt;/a&gt; and have it &lt;a href=&quot;http://www.bloglines.com/sub/http://reviewsby.us/tag/indian&quot;&gt;keep us up to date on new Indian dishes&lt;/a&gt;.  This was simple, especially when much of the work was taken care of by &lt;a href=&quot;http://symfony-project.com/&quot;&gt;the framework&lt;/a&gt;.&lt;/p&gt;

&lt;div id=&quot;footnotes&quot;&gt;
    &lt;hr/&gt;
    &lt;ol&gt;
        &lt;li id=&quot;fn1&quot;&gt;Most clients support &lt;acronym title=&quot;Really Simple Syndication&quot;&gt;RSS&lt;/acronym&gt; so unless there is a compelling need to use Atom or another format, then keeping it down to one choice is always your best bet. &lt;a href=&quot;#fnr1&quot; class=&quot;footnoteBackLink&quot;  title=&quot;Jump back to footnote 1 in the text.&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Comment editing in reviewsBy.us</title>
   <link href="http://davedash.com/2006/07/02/comment-editing-in-reviewsbyus/"/>
   <updated>2006-07-02T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/07/02/comment-editing-in-reviewsbyus</id>
   <content type="html">&lt;p&gt;Something that's been heavily requested for &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsBy.us&lt;/a&gt; is the ability to either preview or somehow edit comments after they are posted.  I took a cue from &lt;a href=&quot;http://digg.com&quot;&gt;digg&lt;/a&gt; and added some comment editing.&lt;/p&gt;

&lt;p&gt;Since the last time we updated, the &lt;acronym title=&quot;real simple syndication&quot;&gt;RSS&lt;/acronym&gt; feeds work better too thanks to &lt;a href=&quot;http://www.symfony-project.com/trac/ticket/585&quot;&gt;a patch&lt;/a&gt; to the &lt;a href=&quot;http://symfony-project.com&quot;&gt;symfony&lt;/a&gt;.  Also, the basis for a user profile is in place.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Random Selections using Propel</title>
   <link href="http://davedash.com/2006/06/12/random-selections-using-propel/"/>
   <updated>2006-06-12T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/06/12/random-selections-using-propel</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://propel.phpdb.org/&quot;&gt;Propel&lt;/a&gt; is a handy way to deal with &lt;acronym title=&quot;Object-Relational Mapping&quot;&gt;ORM&lt;/acronym&gt;.   Rather than figuring out the correct &lt;acronym title=&quot;Structured Query Language&quot;&gt;SQL&lt;/acronym&gt; statement to select your elements you just use a -&lt;code&gt;Peer&lt;/code&gt; object to select it.&lt;/p&gt;

&lt;p&gt;The one drawback is there's no way of choosing an object at random.  You can select the first element of a result set, but not a random one without some changes to your -&lt;code&gt;Peer&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;The quick and dirty fix that I did is to use custom &lt;acronym title=&quot;Structured Query Language&quot;&gt;SQL&lt;/acronym&gt; to populate a &lt;a href=&quot;http://propel.phpdb.org/&quot;&gt;propel&lt;/a&gt; object.  It's a rather suitable approach for more complicated selects.  So here's how we randomly select things:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$con = Propel::getConnection('propel');
$sql = 'SELECT %s.* FROM %s ORDER BY RAND()';
$sql = sprintf($sql,MyObjectPeer::TABLE_NAME,MyObjectPeer::TABLE_NAME);
$stmt = $con-&amp;gt;createStatement();
$rs = $stmt-&amp;gt;executeQuery($sql, ResultSet::FETCHMODE_NUM);
$objects =  MyObjectPeer::populateObjects($rs);
$object = $objects[0];
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you know you're going to only use one object, &lt;code&gt;SELECT %s.* FROM %s ORDER BY RAND() LIMIT 1&lt;/code&gt; will work as well.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Easy Yahoo! Maps and  GeoRSS with symfony</title>
   <link href="http://davedash.com/2006/04/26/easy_yahoo_maps_and_georss_with_symfony/"/>
   <updated>2006-04-26T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/04/26/easy_yahoo_maps_and_georss_with_symfony</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://developer.yahoo.com/maps/georss/index.html&quot;&gt;GeoRSS&lt;/a&gt; is an extension of RSS that incorporates geographic data (i.e. latitude/longitude coordinates).  This is useful for plotting any data that might need to be placed on a map.&lt;/p&gt;

&lt;p&gt;While building out the &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; map, I decided to use the &lt;a href=&quot;http://developer.yahoo.com/maps/index.html&quot;&gt;Yahoo! Maps API&lt;/a&gt; versus the &lt;a href=&quot;http://www.google.com/apis/maps/&quot;&gt;Google Maps API&lt;/a&gt; because I wanted to gain some familiarity with another API.&lt;/p&gt;

&lt;p&gt;It was worth trying &lt;a href=&quot;http://developer.yahoo.com/maps/index.html&quot;&gt;Yahoo!'s API&lt;/a&gt;.  First of all, &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; has addresses for restaurants and Yahoo! provides a simple &lt;a href=&quot;http://developer.yahoo.com/maps/rest/V1/geocode.html&quot;&gt;Geocoding REST&lt;/a&gt; service.  This made it easy for me to convert street addresses to latitude and longitude pairs (even though this wasn't required as we'll soon see).&lt;sup id=&quot;fnr1&quot;&gt;&lt;a href=&quot;#fn1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;  The real selling point of &lt;a href=&quot;http://developer.yahoo.com/maps/index.html&quot;&gt;Yahoo!&lt;/a&gt; was the &lt;a href=&quot;http://developer.yahoo.com/maps/georss/index.html&quot;&gt;GeoRSS&lt;/a&gt; functionality.  I can extend an RSS feed (which &lt;a href=&quot;http://www.symfony-project.com/&quot;&gt;symfony&lt;/a&gt; generates quite easily) to add latitude or longitude points (or even the street address), direct my &lt;a href=&quot;http://developer.yahoo.com/maps/index.html&quot;&gt;Yahoo! map&lt;/a&gt; to the feed and voila, all the locations in that feed are now on the map, and when I click on them, the RSS item is displayed.  That cut down on a lot of development time.&lt;/p&gt;

&lt;!--break--&gt;


&lt;!--more--&gt;


&lt;h3&gt;&lt;a href=&quot;http://developer.yahoo.com/maps/georss/index.html&quot;&gt;Yahoo's GeoRSS&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;The GeoRSS format that Yahoo uses is fairly simple to grasp if you know what an RSS feed looks like.  Here's a typical RSS feed:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&amp;gt;
&amp;lt;rss version=&quot;2.0&quot;&amp;gt;
    &amp;lt;channel&amp;gt;
        &amp;lt;title&amp;gt;Latest restaurants&amp;lt;/title&amp;gt;
        &amp;lt;link&amp;gt;http://reviewsby.us/&amp;lt;/link&amp;gt;
        &amp;lt;description&amp;gt;A list of the latest restaurants posted to my reviewsby.us&amp;lt;/description&amp;gt;
        &amp;lt;language&amp;gt;en&amp;lt;/language&amp;gt;
        &amp;lt;item&amp;gt;
            &amp;lt;title&amp;gt;Bubba Gump Shrimp Co. Restaurant and Market&amp;lt;/title&amp;gt;
            &amp;lt;description&amp;gt;A *Forest Gump* themed restaurant.  Featuring a large selection of seafood items.&amp;lt;/description&amp;gt;
            &amp;lt;link&amp;gt;http://reviewsby.us/restaurant/bubba-gump-shrimp-co-restaurant-and-market&amp;lt;/link&amp;gt;
            &amp;lt;guid&amp;gt;25&amp;lt;/guid&amp;gt;
            &amp;lt;pubDate&amp;gt;Sun, 23 Apr 2006 08:04:00 -0700&amp;lt;/pubDate&amp;gt;
        &amp;lt;/item&amp;gt;
        &amp;lt;item&amp;gt;
            &amp;lt;title&amp;gt;Famous Dave's Barbeque&amp;lt;/title&amp;gt;
            &amp;lt;link&amp;gt;http://reviewsby.us/restaurant/famous-daves-barbeque&amp;lt;/link&amp;gt;
            &amp;lt;guid&amp;gt;24&amp;lt;/guid&amp;gt;
            &amp;lt;pubDate&amp;gt;Wed, 19 Apr 2006 19:58:08 -0700&amp;lt;/pubDate&amp;gt;
        &amp;lt;/item&amp;gt;
    &amp;lt;/channel&amp;gt;
&amp;lt;/rss&amp;gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;item /&amp;gt;&lt;/code&gt; in this example is a restaurant.  To turn this into a GeoRSS feed, we only need to change a few things:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&amp;gt;
&amp;lt;rss version=&quot;2.0&quot; xmlns:geo=&quot;http://www.w3.org/2003/01/geo/wgs84_pos#&quot;&amp;gt;
    &amp;lt;channel&amp;gt;
        &amp;lt;title&amp;gt;Latest restaurants' locations&amp;lt;/title&amp;gt;
        &amp;lt;link&amp;gt;http://reviewsby.us/&amp;lt;/link&amp;gt;
        &amp;lt;description&amp;gt;A geocoded list of the latest restaurants' locations posted to my reviewsby.us&amp;lt;/description&amp;gt;
        &amp;lt;language&amp;gt;en&amp;lt;/language&amp;gt;
        &amp;lt;item&amp;gt;
            &amp;lt;title&amp;gt;Bubba Gump Shrimp Co. Restaurant and Market (Mall of America (Bloomington, MN))&amp;lt;/title&amp;gt;
            &amp;lt;link&amp;gt;http://reviewsby.us/restaurant/bubba-gump-shrimp-co-restaurant-and-market/location/mall-of-america&amp;lt;/link&amp;gt;
            &amp;lt;guid&amp;gt;18&amp;lt;/guid&amp;gt;
            &amp;lt;pubDate&amp;gt;Sun, 23 Apr 2006 08:08:19 -0700&amp;lt;/pubDate&amp;gt;
            &amp;lt;geo:lat&amp;gt;44.85380173&amp;lt;/geo:lat&amp;gt;
            &amp;lt;geo:long&amp;gt;-93.24040222&amp;lt;/geo:long&amp;gt;
        &amp;lt;/item&amp;gt;
    &amp;lt;/channel&amp;gt;
&amp;lt;/rss&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We just added &lt;code&gt;xmlns:geo=&quot;http://www.w3.org/2003/01/geo/wgs84_pos#&quot;&lt;/code&gt; as an attribute to the &lt;code&gt;&amp;lt;rss/&amp;gt;&lt;/code&gt; tag and added &lt;code&gt;&amp;lt;geo:lat /&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;geo:long /&amp;gt;&lt;/code&gt; tags to any given item.  If you'd rather use address, city, state and zip fields instead of latitude and longitude coordinates, &lt;a href=&quot;http://developer.yahoo.com/maps/georss/index.html&quot;&gt;the Yahoo! Georss page&lt;/a&gt; will tell you how.&lt;/p&gt;

&lt;h3&gt;&lt;a href=&quot;http://www.symfony-project.com/&quot;&gt;symfony&lt;/a&gt; and Geodata&lt;/h3&gt;

&lt;p&gt;I knew full well that &lt;a href=&quot;http://developer.yahoo.com/maps/index.html&quot;&gt;Yahoo! Maps&lt;/a&gt; did not require me to have everything in latitude longitude coordinates, but I felt that from an efficiency standpoint, it made more sense for me to convert them once using a &lt;a href=&quot;http://developer.yahoo.com/maps/rest/V1/geocode.html&quot;&gt;geocoder&lt;/a&gt; and then Yahoo! wouldn't have to translate them later.  Also, I'm trying to think ahead when more than just Minneapolis restaurants become a part of the &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; site, I now have an easy way of determining distance between a user's home and a given restaurant.  Also, if Yahoo! Maps doesn't work out, I can use these coordinates in other mapping systems.&lt;/p&gt;

&lt;h4&gt;Extending the model&lt;/h4&gt;

&lt;p&gt;Models in &lt;a href=&quot;http://www.symfony-project.com/&quot;&gt;symfony&lt;/a&gt; lend themselves to being easily extended.  So we can easily take a model for a location:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;table name=&quot;location&quot; phpName=&quot;Location&quot;&amp;gt;
    &amp;lt;column name=&quot;id&quot; type=&quot;integer&quot; primaryKey=&quot;true&quot; autoIncrement=&quot;true&quot;/&amp;gt;
    &amp;lt;column name=&quot;restaurant_id&quot; type=&quot;integer&quot; /&amp;gt;
    &amp;lt;foreign-key foreignTable=&quot;restaurant&quot;&amp;gt;
        &amp;lt;reference local=&quot;restaurant_id&quot; foreign=&quot;id&quot;/&amp;gt;
    &amp;lt;/foreign-key&amp;gt;
    &amp;lt;column name=&quot;stripped_title&quot; type=&quot;varchar&quot; size=&quot;255&quot; /&amp;gt;
    &amp;lt;column name=&quot;name&quot; type=&quot;varchar&quot; size=&quot;255&quot; /&amp;gt;
    &amp;lt;column name=&quot;address&quot; type=&quot;varchar&quot; size=&quot;255&quot; /&amp;gt;
    &amp;lt;column name=&quot;city&quot; type=&quot;varchar&quot; size=&quot;128&quot; /&amp;gt;
    &amp;lt;column name=&quot;state&quot; type=&quot;varchar&quot; size=&quot;16&quot; /&amp;gt;
    &amp;lt;column name=&quot;zip&quot; type=&quot;varchar&quot; size=&quot;9&quot; /&amp;gt;
    &amp;lt;column name=&quot;phone&quot; type=&quot;varchar&quot; size=&quot;16&quot; /&amp;gt;
    &amp;lt;column name=&quot;approved&quot; type=&quot;boolean&quot; /&amp;gt;
    &amp;lt;column name=&quot;updated_at&quot; type=&quot;TIMESTAMP&quot; /&amp;gt;
    &amp;lt;column name=&quot;created_at&quot; type=&quot;TIMESTAMP&quot; /&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and simply add two columns inside the &lt;code&gt;&amp;lt;table/&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;column name=&quot;latitude&quot; type=&quot;float&quot; size=&quot;10&quot; scale=&quot;8&quot;/&amp;gt;
&amp;lt;column name=&quot;longitude&quot; type=&quot;float&quot; size=&quot;10&quot; scale=&quot;8&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Easy!  However, we don't want to have to set the latitude and longitude by hand each time we update a &lt;code&gt;Location&lt;/code&gt;.  So first we write a function that takes an address and converts it to latitude/longitude.  I placed mine in a &lt;code&gt;myTools.class.php&lt;/code&gt; in my &lt;code&gt;lib&lt;/code&gt; folder:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class myTools
{
    public static function getLatLng($address, $city = null, $state = null, $zip = null)
    {
        $url = sfConfig::get('app_yahoo_geocode');
        $query['appid'] = sfConfig::get('app_yahoo_app_id');
        $query['street'] = $address;
        $query['city'] = $city;
        $query['state'] = $state;
        $query['zip'] = $zip;
        $query['output'] = 'php'; 
        $url .= '?' . http_build_query($query); 
        $response = @file_get_contents($url);
        if ($response) {
            $response = unserialize($response);
            return $response['ResultSet']['Result'];
        }
        return null;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Defined in my &lt;code&gt;app.yml&lt;/code&gt; is &lt;code&gt;app_yahoo_geocode&lt;/code&gt; and my &lt;code&gt;app_yahoo_app_id&lt;/code&gt;.  &lt;code&gt;myTools::getLatLng()&lt;/code&gt; queries the &lt;a href=&quot;http://developer.yahoo.com/maps/rest/V1/geocode.html&quot;&gt;Yahoo! REST&lt;/a&gt; service and returns the coordinates that Yahoo! delivers.  Note that the generated query string includes &lt;code&gt;output=php&lt;/code&gt;.  Yahoo! supports serializing output as PHP instead of XML.  This can save a bundle of time over decoding XML.&lt;/p&gt;

&lt;p&gt;So now let's look at our &lt;code&gt;Location.php&lt;/code&gt; and override its inherited save function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public function save($con = null)
{
    // save latitude and longitude
    $locdata = myTools::getLatLng($this-&amp;gt;getAddress(), $this-&amp;gt;getCity(), $this-&amp;gt;getState(), $this-&amp;gt;getZip());
    if ($locdata) 
    {
        $this-&amp;gt;setLatitude($locdata['Latitude']);
        $this-&amp;gt;setLongitude($locdata['Longitude']);
    }
    parent::save($con);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you stop here, you'll at least now know how to add latitude and longitude coordinates to an object automagically.&lt;/p&gt;

&lt;h4&gt;Producing a GeoRSS feed&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;http://www.symfony-project.com/&quot;&gt;symfony&lt;/a&gt; very easily will allow you to &lt;a href=&quot;http://www.symfony-project.com/content/book/page/syndication.html&quot;&gt;generate an RSS feed&lt;/a&gt;.  How do we create a &lt;a href=&quot;http://developer.yahoo.com/maps/georss/index.html&quot;&gt;GeoRSS&lt;/a&gt; feed?  Just extend the &lt;code&gt;sfFeed&lt;/code&gt; class.  Rather than instantiating a feed like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$feed = sfFeed::newInstance('rss201rev2');
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We do this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$feed = sfFeed::newInstance('geoRSS');  
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And then create an &lt;a href=&quot;http://spindrop.us/files/symfony/sfGeoRssFeed.class.txt&quot;&gt;sfGeoRssFeed.class.php&lt;/a&gt; and we're done.  We've created a GeoRSS feed fairly easily.  Comb through &lt;a href=&quot;http://spindrop.us/files/symfony/sfGeoRssFeed.class.txt&quot;&gt;sfGeoRssFeed.class.php&lt;/a&gt; and compare it to the &lt;a href=&quot;http://www.symfony-project.com/trac/browser/trunk/lib/symfony/addon/sfFeed/sfRss201rev2Feed.class.php?rev=403&quot;&gt;sfRss201rev2Feed.class.php&lt;/a&gt;, you'll notice it's not that different and that it's fairly easy to extend the sfFeed plugin for &lt;a href=&quot;http://www.symfony-project.com/&quot;&gt;symfony&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Adding your feed to a Yahoo! Map.&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://developer.yahoo.com/maps/ajax/index.html#ex6&quot;&gt;Adding a GeoRSS feed to Yahoo! maps&lt;/a&gt; is simple.  Before I embedded the RSS feed into my Yahoo! Map I was prepared to write an algorithm to cluster only on the points in my RSS feed, lucky for me (and you), &lt;a href=&quot;http://developer.yahoo.com/maps/index.html&quot;&gt;Yahoo! Maps&lt;/a&gt; does this automatically.  One pitfall you might reach during development is that Yahoo! Maps must be able to reach your GeoRSS feed.  My development machine is my personal laptop, so this didn't work so well until I uploaded to a publically accessible staging server.  &lt;a href=&quot;http://reviewsby.us/&quot;&gt;The maps&lt;/a&gt; worked like a charm as you can see.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://developer.yahoo.com/maps/index.html&quot;&gt;Yahoo! Maps&lt;/a&gt; are very powerful, and &lt;a href=&quot;http://www.symfony-project.com/&quot;&gt;symfony&lt;/a&gt; is up to the task.  I hope you found this tutorial useful.  If you have any trouble, let me know.  I hope your &lt;a href=&quot;http://reviewsby.us/&quot;&gt;next meal&lt;/a&gt; is a good one.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;
    &lt;ol&gt;
        &lt;li id=&quot;fn1&quot;&gt;
            &lt;p&gt;
                Using Yahoo! Maps wasn't a requisite of using the Yahoo! Geocoder.  
                The datasets that were returned could have been used to populate a Google Map just as easily.
                &lt;a href=&quot;#fnr1&quot;  class=&quot;footnoteBackLink&quot;  title=&quot;Jump back to footnote 1 in the text.&quot;&gt;&amp;#8617;&lt;/a&gt;
            &lt;/p&gt;
        &lt;/li&gt;
    &lt;/ol&gt;
&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Easy Yahoo! Maps and  GeoRSS with symfony</title>
   <link href="http://davedash.com/2006/04/26/easy_yahoo_maps_and_georss_with_symfony/"/>
   <updated>2006-04-26T00:00:00-07:00</updated>
   <id>http://davedash.com/2006/04/26/easy_yahoo_maps_and_georss_with_symfony</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://developer.yahoo.com/maps/georss/index.html&quot;&gt;GeoRSS&lt;/a&gt; is an extension of RSS that incorporates geographic data (i.e. latitude/longitude coordinates).  This is useful for plotting any data that might need to be placed on a map.&lt;/p&gt;

&lt;p&gt;While building out the &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; map, I decided to use the &lt;a href=&quot;http://developer.yahoo.com/maps/index.html&quot;&gt;Yahoo! Maps API&lt;/a&gt; versus the &lt;a href=&quot;http://www.google.com/apis/maps/&quot;&gt;Google Maps API&lt;/a&gt; because I wanted to gain some familiarity with another API.&lt;/p&gt;

&lt;p&gt;It was worth trying &lt;a href=&quot;http://developer.yahoo.com/maps/index.html&quot;&gt;Yahoo!'s API&lt;/a&gt;.  First of all, &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; has addresses for restaurants and Yahoo! provides a simple &lt;a href=&quot;http://developer.yahoo.com/maps/rest/V1/geocode.html&quot;&gt;Geocoding REST&lt;/a&gt; service.  This made it easy for me to convert street addresses to latitude and longitude pairs (even though this wasn't required as we'll soon see).&lt;sup id=&quot;fnr1&quot;&gt;&lt;a href=&quot;#fn1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;  The real selling point of &lt;a href=&quot;http://developer.yahoo.com/maps/index.html&quot;&gt;Yahoo!&lt;/a&gt; was the &lt;a href=&quot;http://developer.yahoo.com/maps/georss/index.html&quot;&gt;GeoRSS&lt;/a&gt; functionality.  I can extend an RSS feed (which &lt;a href=&quot;http://www.symfony-project.com/&quot;&gt;symfony&lt;/a&gt; generates quite easily) to add latitude or longitude points (or even the street address), direct my &lt;a href=&quot;http://developer.yahoo.com/maps/index.html&quot;&gt;Yahoo! map&lt;/a&gt; to the feed and voila, all the locations in that feed are now on the map, and when I click on them, the RSS item is displayed.  That cut down on a lot of development time.&lt;/p&gt;

&lt;!--break--&gt;


&lt;!--more--&gt;


&lt;h3&gt;&lt;a href=&quot;http://developer.yahoo.com/maps/georss/index.html&quot;&gt;Yahoo's GeoRSS&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;The GeoRSS format that Yahoo uses is fairly simple to grasp if you know what an RSS feed looks like.  Here's a typical RSS feed:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&amp;gt;
&amp;lt;rss version=&quot;2.0&quot;&amp;gt;
    &amp;lt;channel&amp;gt;
        &amp;lt;title&amp;gt;Latest restaurants&amp;lt;/title&amp;gt;
        &amp;lt;link&amp;gt;http://reviewsby.us/&amp;lt;/link&amp;gt;
        &amp;lt;description&amp;gt;A list of the latest restaurants posted to my reviewsby.us&amp;lt;/description&amp;gt;
        &amp;lt;language&amp;gt;en&amp;lt;/language&amp;gt;
        &amp;lt;item&amp;gt;
            &amp;lt;title&amp;gt;Bubba Gump Shrimp Co. Restaurant and Market&amp;lt;/title&amp;gt;
            &amp;lt;description&amp;gt;A *Forest Gump* themed restaurant.  Featuring a large selection of seafood items.&amp;lt;/description&amp;gt;
            &amp;lt;link&amp;gt;http://reviewsby.us/restaurant/bubba-gump-shrimp-co-restaurant-and-market&amp;lt;/link&amp;gt;
            &amp;lt;guid&amp;gt;25&amp;lt;/guid&amp;gt;
            &amp;lt;pubDate&amp;gt;Sun, 23 Apr 2006 08:04:00 -0700&amp;lt;/pubDate&amp;gt;
        &amp;lt;/item&amp;gt;
        &amp;lt;item&amp;gt;
            &amp;lt;title&amp;gt;Famous Dave's Barbeque&amp;lt;/title&amp;gt;
            &amp;lt;link&amp;gt;http://reviewsby.us/restaurant/famous-daves-barbeque&amp;lt;/link&amp;gt;
            &amp;lt;guid&amp;gt;24&amp;lt;/guid&amp;gt;
            &amp;lt;pubDate&amp;gt;Wed, 19 Apr 2006 19:58:08 -0700&amp;lt;/pubDate&amp;gt;
        &amp;lt;/item&amp;gt;
    &amp;lt;/channel&amp;gt;
&amp;lt;/rss&amp;gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;item /&amp;gt;&lt;/code&gt; in this example is a restaurant.  To turn this into a GeoRSS feed, we only need to change a few things:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&amp;gt;
&amp;lt;rss version=&quot;2.0&quot; xmlns:geo=&quot;http://www.w3.org/2003/01/geo/wgs84_pos#&quot;&amp;gt;
    &amp;lt;channel&amp;gt;
        &amp;lt;title&amp;gt;Latest restaurants' locations&amp;lt;/title&amp;gt;
        &amp;lt;link&amp;gt;http://reviewsby.us/&amp;lt;/link&amp;gt;
        &amp;lt;description&amp;gt;A geocoded list of the latest restaurants' locations posted to my reviewsby.us&amp;lt;/description&amp;gt;
        &amp;lt;language&amp;gt;en&amp;lt;/language&amp;gt;
        &amp;lt;item&amp;gt;
            &amp;lt;title&amp;gt;Bubba Gump Shrimp Co. Restaurant and Market (Mall of America (Bloomington, MN))&amp;lt;/title&amp;gt;
            &amp;lt;link&amp;gt;http://reviewsby.us/restaurant/bubba-gump-shrimp-co-restaurant-and-market/location/mall-of-america&amp;lt;/link&amp;gt;
            &amp;lt;guid&amp;gt;18&amp;lt;/guid&amp;gt;
            &amp;lt;pubDate&amp;gt;Sun, 23 Apr 2006 08:08:19 -0700&amp;lt;/pubDate&amp;gt;
            &amp;lt;geo:lat&amp;gt;44.85380173&amp;lt;/geo:lat&amp;gt;
            &amp;lt;geo:long&amp;gt;-93.24040222&amp;lt;/geo:long&amp;gt;
        &amp;lt;/item&amp;gt;
    &amp;lt;/channel&amp;gt;
&amp;lt;/rss&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We just added &lt;code&gt;xmlns:geo=&quot;http://www.w3.org/2003/01/geo/wgs84_pos#&quot;&lt;/code&gt; as an attribute to the &lt;code&gt;&amp;lt;rss/&amp;gt;&lt;/code&gt; tag and added &lt;code&gt;&amp;lt;geo:lat /&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;geo:long /&amp;gt;&lt;/code&gt; tags to any given item.  If you'd rather use address, city, state and zip fields instead of latitude and longitude coordinates, &lt;a href=&quot;http://developer.yahoo.com/maps/georss/index.html&quot;&gt;the Yahoo! Georss page&lt;/a&gt; will tell you how.&lt;/p&gt;

&lt;h3&gt;&lt;a href=&quot;http://www.symfony-project.com/&quot;&gt;symfony&lt;/a&gt; and Geodata&lt;/h3&gt;

&lt;p&gt;I knew full well that &lt;a href=&quot;http://developer.yahoo.com/maps/index.html&quot;&gt;Yahoo! Maps&lt;/a&gt; did not require me to have everything in latitude longitude coordinates, but I felt that from an efficiency standpoint, it made more sense for me to convert them once using a &lt;a href=&quot;http://developer.yahoo.com/maps/rest/V1/geocode.html&quot;&gt;geocoder&lt;/a&gt; and then Yahoo! wouldn't have to translate them later.  Also, I'm trying to think ahead when more than just Minneapolis restaurants become a part of the &lt;a href=&quot;http://reviewsby.us/&quot;&gt;reviewsby.us&lt;/a&gt; site, I now have an easy way of determining distance between a user's home and a given restaurant.  Also, if Yahoo! Maps doesn't work out, I can use these coordinates in other mapping systems.&lt;/p&gt;

&lt;h4&gt;Extending the model&lt;/h4&gt;

&lt;p&gt;Models in &lt;a href=&quot;http://www.symfony-project.com/&quot;&gt;symfony&lt;/a&gt; lend themselves to being easily extended.  So we can easily take a model for a location:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;table name=&quot;location&quot; phpName=&quot;Location&quot;&amp;gt;
    &amp;lt;column name=&quot;id&quot; type=&quot;integer&quot; primaryKey=&quot;true&quot; autoIncrement=&quot;true&quot;/&amp;gt;
    &amp;lt;column name=&quot;restaurant_id&quot; type=&quot;integer&quot; /&amp;gt;
    &amp;lt;foreign-key foreignTable=&quot;restaurant&quot;&amp;gt;
        &amp;lt;reference local=&quot;restaurant_id&quot; foreign=&quot;id&quot;/&amp;gt;
    &amp;lt;/foreign-key&amp;gt;
    &amp;lt;column name=&quot;stripped_title&quot; type=&quot;varchar&quot; size=&quot;255&quot; /&amp;gt;
    &amp;lt;column name=&quot;name&quot; type=&quot;varchar&quot; size=&quot;255&quot; /&amp;gt;
    &amp;lt;column name=&quot;address&quot; type=&quot;varchar&quot; size=&quot;255&quot; /&amp;gt;
    &amp;lt;column name=&quot;city&quot; type=&quot;varchar&quot; size=&quot;128&quot; /&amp;gt;
    &amp;lt;column name=&quot;state&quot; type=&quot;varchar&quot; size=&quot;16&quot; /&amp;gt;
    &amp;lt;column name=&quot;zip&quot; type=&quot;varchar&quot; size=&quot;9&quot; /&amp;gt;
    &amp;lt;column name=&quot;phone&quot; type=&quot;varchar&quot; size=&quot;16&quot; /&amp;gt;
    &amp;lt;column name=&quot;approved&quot; type=&quot;boolean&quot; /&amp;gt;
    &amp;lt;column name=&quot;updated_at&quot; type=&quot;TIMESTAMP&quot; /&amp;gt;
    &amp;lt;column name=&quot;created_at&quot; type=&quot;TIMESTAMP&quot; /&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and simply add two columns inside the &lt;code&gt;&amp;lt;table/&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;column name=&quot;latitude&quot; type=&quot;float&quot; size=&quot;10&quot; scale=&quot;8&quot;/&amp;gt;
&amp;lt;column name=&quot;longitude&quot; type=&quot;float&quot; size=&quot;10&quot; scale=&quot;8&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Easy!  However, we don't want to have to set the latitude and longitude by hand each time we update a &lt;code&gt;Location&lt;/code&gt;.  So first we write a function that takes an address and converts it to latitude/longitude.  I placed mine in a &lt;code&gt;myTools.class.php&lt;/code&gt; in my &lt;code&gt;lib&lt;/code&gt; folder:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class myTools
{
    public static function getLatLng($address, $city = null, $state = null, $zip = null)
    {
        $url = sfConfig::get('app_yahoo_geocode');
        $query['appid'] = sfConfig::get('app_yahoo_app_id');
        $query['street'] = $address;
        $query['city'] = $city;
        $query['state'] = $state;
        $query['zip'] = $zip;
        $query['output'] = 'php'; 
        $url .= '?' . http_build_query($query); 
        $response = @file_get_contents($url);
        if ($response) {
            $response = unserialize($response);
            return $response['ResultSet']['Result'];
        }
        return null;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Defined in my &lt;code&gt;app.yml&lt;/code&gt; is &lt;code&gt;app_yahoo_geocode&lt;/code&gt; and my &lt;code&gt;app_yahoo_app_id&lt;/code&gt;.  &lt;code&gt;myTools::getLatLng()&lt;/code&gt; queries the &lt;a href=&quot;http://developer.yahoo.com/maps/rest/V1/geocode.html&quot;&gt;Yahoo! REST&lt;/a&gt; service and returns the coordinates that Yahoo! delivers.  Note that the generated query string includes &lt;code&gt;output=php&lt;/code&gt;.  Yahoo! supports serializing output as PHP instead of XML.  This can save a bundle of time over decoding XML.&lt;/p&gt;

&lt;p&gt;So now let's look at our &lt;code&gt;Location.php&lt;/code&gt; and override its inherited save function:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public function save($con = null)
{
    // save latitude and longitude
    $locdata = myTools::getLatLng($this-&amp;gt;getAddress(), $this-&amp;gt;getCity(), $this-&amp;gt;getState(), $this-&amp;gt;getZip());
    if ($locdata) 
    {
        $this-&amp;gt;setLatitude($locdata['Latitude']);
        $this-&amp;gt;setLongitude($locdata['Longitude']);
    }
    parent::save($con);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you stop here, you'll at least now know how to add latitude and longitude coordinates to an object automagically.&lt;/p&gt;

&lt;h4&gt;Producing a GeoRSS feed&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;http://www.symfony-project.com/&quot;&gt;symfony&lt;/a&gt; very easily will allow you to &lt;a href=&quot;http://www.symfony-project.com/content/book/page/syndication.html&quot;&gt;generate an RSS feed&lt;/a&gt;.  How do we create a &lt;a href=&quot;http://developer.yahoo.com/maps/georss/index.html&quot;&gt;GeoRSS&lt;/a&gt; feed?  Just extend the &lt;code&gt;sfFeed&lt;/code&gt; class.  Rather than instantiating a feed like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$feed = sfFeed::newInstance('rss201rev2');
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We do this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$feed = sfFeed::newInstance('geoRSS');  
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And then create an &lt;a href=&quot;http://spindrop.us/files/symfony/sfGeoRssFeed.class.txt&quot;&gt;sfGeoRssFeed.class.php&lt;/a&gt; and we're done.  We've created a GeoRSS feed fairly easily.  Comb through &lt;a href=&quot;http://spindrop.us/files/symfony/sfGeoRssFeed.class.txt&quot;&gt;sfGeoRssFeed.class.php&lt;/a&gt; and compare it to the &lt;a href=&quot;http://www.symfony-project.com/trac/browser/trunk/lib/symfony/addon/sfFeed/sfRss201rev2Feed.class.php?rev=403&quot;&gt;sfRss201rev2Feed.class.php&lt;/a&gt;, you'll notice it's not that different and that it's fairly easy to extend the sfFeed plugin for &lt;a href=&quot;http://www.symfony-project.com/&quot;&gt;symfony&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Adding your feed to a Yahoo! Map.&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://developer.yahoo.com/maps/ajax/index.html#ex6&quot;&gt;Adding a GeoRSS feed to Yahoo! maps&lt;/a&gt; is simple.  Before I embedded the RSS feed into my Yahoo! Map I was prepared to write an algorithm to cluster only on the points in my RSS feed, lucky for me (and you), &lt;a href=&quot;http://developer.yahoo.com/maps/index.html&quot;&gt;Yahoo! Maps&lt;/a&gt; does this automatically.  One pitfall you might reach during development is that Yahoo! Maps must be able to reach your GeoRSS feed.  My development machine is my personal laptop, so this didn't work so well until I uploaded to a publically accessible staging server.  &lt;a href=&quot;http://reviewsby.us/&quot;&gt;The maps&lt;/a&gt; worked like a charm as you can see.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://developer.yahoo.com/maps/index.html&quot;&gt;Yahoo! Maps&lt;/a&gt; are very powerful, and &lt;a href=&quot;http://www.symfony-project.com/&quot;&gt;symfony&lt;/a&gt; is up to the task.  I hope you found this tutorial useful.  If you have any trouble, let me know.  I hope your &lt;a href=&quot;http://reviewsby.us/&quot;&gt;next meal&lt;/a&gt; is a good one.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot;&gt;
    &lt;ol&gt;
        &lt;li id=&quot;fn1&quot;&gt;
            &lt;p&gt;
                Using Yahoo! Maps wasn't a requisite of using the Yahoo! Geocoder.  
                The datasets that were returned could have been used to populate a Google Map just as easily.
                &lt;a href=&quot;#fnr1&quot;  class=&quot;footnoteBackLink&quot;  title=&quot;Jump back to footnote 1 in the text.&quot;&gt;&amp;#8617;&lt;/a&gt;
            &lt;/p&gt;
        &lt;/li&gt;
    &lt;/ol&gt;
&lt;/div&gt;

</content>
 </entry>
 

</feed>

