Symfony

Symfony – Remove .php From Controller using Symlink

1

Symfony is great, I get it, but there are certain situations where the seemingly easiest task may take days to uncover. If you are reading this post then chances are you are trying to figure out an elegant approach to removing the .php from your controllers. I had to jump down the same rabbit hole, and after days of research and scouring I finally got it working with minimal changes.

There are several posts on this subject via google searches, some using rewrite rules, others using the ‘no_script_name’ in settings.yml. I think you will find the following approach to be the best. There are two config set-ups; the first assumes your site is hosted via dedicated host, the second assumes you are using the .htaccess file packaged with symfony in the /web directory. This article was developed / tested using the dedicated hosting solution and has a high probability of working, the virtual host solution is an assumed theory and should work, but may need some minor tweaks.

Dedicated Hosting

If you are fortunate enough to have dedicated hosting, then you (or a sys admin) have access to the apache config files needed to make this approach work. This solution assumes you are using a virtual host config files, if you are not, you really should. Virtual host config files keep your primary httpd.conf file clean and uncluttered. I am not going to go into detail on how to setup virtual host config files here, but you can find many an article on the subject via google. The one thing you should know, is that when conf files are loaded they are loaded alphetically from the directory you specify in your httpd.conf file (something like: Include conf.d/*.conf).

What I did is create a file in the conf.d directory like 010_symfony.conf, so my dir looked like:

mpurcell@dev1 ~ $ -> ls /etc/httpd/conf/vhost.d/
total 16
drwxr-xr-x 2 mpurcell webdev 4096 Jun  9 07:43 .
drwxr-xr-x 3 mpurcell webdev 4096 Jun 14 07:58 ..
-rw-r--r-- 1 mpurcell webdev    0 Jun  1 10:36 000_global.conf
-rw-r--r-- 1 mpurcell webdev   81 Jun 14 13:13 010_symfony.conf
-rw-r--r-- 1 mpurcell webdev 1681 Jun 14 20:05 999_default.conf

As you can see from the dir listing, I have 3 conf files. The global file at this point is empty, it’s just a footprint if I ever need to add anything that needs to be considered before all other config files. The next file to be loaded is the 010_symfony.conf file which the contents look like:

mpurcell@dev1 ~ $ -> cat /etc/httpd/conf/vhost.d/010_symfony.conf

<FilesMatch "^(fe|be)?(_dev)?$">
    ForceType application/x-httpd-php
</FilesMatch>

The Apache directive; ‘FilesMatch’ allows for regex matching of the URI. In the example above I am looking for any urls that have fe (frontend app) and be (backend app) along with their associated dev controllers (_dev). If there is a match, then the ForceType kicks in and tells Apache that the matching symlink should be treated as if it were an actual php file. Be sure to restart your apache instance for these changes to take effect.

Now that we have this directive in place, lets setup the necessary symlinks in the symfony /web directory.

cd /path/to/symfony/web

ln -s fe.php fe

ln -s fe_dev.php fe_dev

ln -s be.php be

ln -s be_dev.php be_dev

As you can see we created four symlinks, each one pointing to a specific controller. As mentioned above, ‘fe’ is short for ‘frontend’, and ‘be’ is short for ‘backend’. You can change these to whatever you like, if you would rather use ‘admin’ simply create the symlink then update the /etc/httpd/conf/vhost.d/010_symfony.conf to look like:

<FilesMatch "^(fe|be|admin)?(_dev)?$">
    ForceType application/x-httpd-php
</FilesMatch>

Notice that I added ‘admin’ to the FilesMatch regex, so now if you have a symlink of ‘admin’ pointing to be.php, it will work. Remember this is OPTIONAL and simply demonstrates the flexibility of this overall symlink approach for removing .php from your controllers.

Now, if you use one of your symlinked controllers you may notice an issue with the links symfony constructs. For me, they doubled up the controller name so my links looked something like: ‘/be_dev/be_dev/user/login’. This issue is where I spent most of my time resolving, I thought maybe it was an apache config setting, I had no idea it was under the symfony hood. To fix this issue you need to do the following:

vi /path/to/symfony/app//factories.yml

#If you don't already have the following activated you will need to make sure it's active under the 'all' directive

all:

...

request:
  class: sfWebRequest #if you are using custom class be sure to replace this with your custom class name
  param:
    logging:           %SF_LOGGING_ENABLED%
    path_info_array:   SERVER
    path_info_key:     PATH_INFO
    relative_url_root: ""

...

The real magic lies in the ‘relative_url_root’ directive. According to symfony docs, it tells symfony to prefix all links etc with this value. So if you had your files in a /web/blog dir you could set this to /blog. In the solution discussed here, we are using symlinks in the root /web dir, so instead of setting relative_url_root to a value, we just nullify it. By nullifying relative_url_root this results in symonfy not doubling up the controller in subsequent links. So the issue described above where links resulted in /be_dev/be_dev/user/login, should now result in /be_dev/user/login, which is what we want. Also be sure that you make the above changes to all your apps factories.yml files so they behave the same way.

Now, clear symfony cache and try your url again, you should see your page behave as if you went through the native controller (be_dev.php). So http://yourhost.com/be_dev.php should behave exactly the same as http://yourhost.com/be_dev

Pretty cool right!?

Virtual Hosting

For the same behavior to work on a site which is virtually hosted, you simply need to add the aformentioned ‘FilesMatch’ directive to your /web/.htaccess file so that it looks something like:

<FilesMatch "^(fe|be)?(_dev)?$">
    ForceType application/x-httpd-php
</FilesMatch>

# Settings below this line are symfony defaults no need to edit

Options +FollowSymLinks +ExecCGI

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]

Closing

Hopefully the solutions provided above work for your and your specific circumstances, if not feel free to add a comment and we can see what the issue is. I feel that the solution presented is elegant and easy to implement no matter what your situation. Good luck!

Symfony – Add ReCaptcha to JQuery Dialog (Lightbox)

5

The following solution of adding ReCaptcha to a JQuery Dialog (Lightbox) makes use of the sfFormExtraPlugin, not the sfReCaptchaPlugin. The reason is because the following solution was developed in Symfony 1.2+, and we had access to the Symfony form architecture.

Out of the box, the sfFormExtraPlugin sfWidgetFormReCaptcha::render() will not work in lightboxes because it results in a document.write(). Consider the following:

// sfWidgetFormReCaptcha::render() produces the following javascript
<script type="text/javascript" src="http://api.recaptcha.net/challenge?k=6Let7r0SAAAAAJFp77tW4gRze7r5y89Cql_msPcm"></script>

// Which produces the following javascript
var RecaptchaState = {
    site : '6Let7r0SAAAAAJFp77tW4gRze7r5y89Cql_msPcm',
    challenge : '03AHJ_VusnO7jeI3VF-cHgOIv9RY9RIpYl2QWea_LULIdL8xrM9PZdcZZ7I9gFf4gSbr1fxGCSQjQZPJQ1sa6p1oEI9U_nkU8f2SjczxjH6nzmy43Q-m_8rnxWhhRUIDa7iTPEwo4-dwi-FipyMHsSAz-nE5yfFQfZog',
    is_incorrect : false,
    programming_error : '',
    error_message : '',
    server : 'http://www.google.com/recaptcha/api/',
    timeout : 18000
};

document.write('<scr'+'ipt type="text/javascript" s'+'rc="' + RecaptchaState.server + 'js/recaptcha.js"></scr'+'ipt>');</pre>

Note the resulting document.write() code produced by recaptcha.

Recaptha offers multiple ways to access their api, so instead of the solution implemented by the devs of the sfWidgetFormReCaptcha plugin, I opted to introduce custom code which will produce the same result, except it will be lightbox friendly.  As with any Symfony plugin, I made sure not to overwrite core logic so it doesn’t increase technical debt with respect to future plugin upgrades etc. (unless the core logic changes such that the custom code must follow suit).

First, we need to override the default plugin code so we can add our custom code, and thanks to Symfony autoloading, the following changes should work out of the box. Also note, the myWidgetFormCaptcha class name follows symfony naming conventions to indicate Symfony core code vs custom code.

We need to create a new file which will extend sfWidgetFormReCaptcha;

mkdir -p /path/to/symfony_code/lib/form/myExtraFormPlugin/lib/widget
touch /path/to/symfony_code/lib/form/myExtraFormPlugin/lib/widget/myWidgetFormReCaptcha.class.php #lol @ touch myWidget

You can use whichever path you wish, just make sure it’s in the /path/to/symfony_code/lib dir so it will be included when Symfony autoloads files. I chose the above path to maximize flexibility if I ever need to override any other sfFormExtraPlugin widgets.

Now that we have our override class file in place, lets add the code:

<?php
// /path/to/symfony_code/lib/form/myExtraFormPlugin/lib/widget/myWidgetFormReCaptcha.class.php

/**
 * Extending plugin functionality, see parent class for full docs
 */
class myWidgetFormReCaptcha extends sfWidgetFormReCaptcha
{
 /**
 * @see sfWidgetForm
 */
public function render($name, $value = null, $attributes = array(), $errors = array())
{
    $server    = $this->getServerUrl();
    $key    = $this->getOption('public_key');

    if ((array_key_exists('context', $attributes)) &&
        ($attributes['context'] == 'lightbox')) {

        // Arbitrary flag, unset it
        unset($attributes['context']);

        return javascript_tag("
            Recaptcha.create('".$key."', 'captchaWrap', {custom_translations: {instructions_visual:'" . __('recaptcha_instructions_visual') . "',instructions_audio:'" . __('recaptcha_instructions_audio') . "',play_again:'" . __('recaptcha_play_again') . "', cant_hear_this:'" . __('recaptcha_cant_hear_this') . "', visual_challenge:'" . __('recaptcha_visual_challenge') . "', audio_challenge:'" . __('recaptcha_audio_challenge') . "', refresh_btn:'" . __('recaptcha_refresh_btn') . "', help_btn:'" . __('recaptcha_help_btn') . "', incorrect_try_again:'" . __('recaptcha_incorrect_try_again') . "'}, theme:'". sfConfig::get('recaptchaTheme', sfConfig::get('clientid'), 'text', 'clean') ."', lang:'en'});
        ") . '<div id="captchaWrap"></div>';
    }

    parent::render($name, $value, $attributes, $errors);
}

Ya I know, a lot of javascript, but nothing overly complicated just a bunch of flags dictating to recaptcha how the element should be rendered. I added an arbitary flag to the attributes array, so I could still call the same render() methods.  This allows the calling code to tell the widget how it should be rendered. If the context flag is not set to ‘lightbox’ then it simply calls on parent::render() which will default to normal behavior for the class.

Now we just need to add the following code to the lightbox template (the template that your form data is located in):

<?php echo $form['captcha']->renderRow(array('context' => 'lightbox')); ?>

And of course we need to bring in the ReCaptcha js library (view.yml):

javascripts: [http://www.google.com/recaptcha/api/js/recaptcha_ajax.js]

Now, clear symfony cache and click the link to activate your lightbox, it should render your form AND the recaptcha form element. Yay!

In a future post I will write-up how to check lightbox ajax responses using JSON.

Symfony – Custom (Private) Plugins – Naming Convention

0

If you ever tried to create your own plugin and got an error message similar to the following; “Uncaught exception ‘InvalidArgumentException’ with message ‘The plugin “myPlugin” does not exist.’ it’s because you didn’t name your plugin folder correctly.

Consider the following:


#bad

/plugins/myCustom

#good

/plugins/myCustomPlugin

When I was googling for the cause of the above exception there were no readily available answers, even via Symfony’s docs for plug-ins. All I could find via Symfony’s site about the plug-ins was a blurb about the difference between Private and Public plug-ins, but now how to manage private plug-ins.

So, not being able to find the issue via google, I checked the source code and found that in line #482 of /lib/config/sfProjectConfiguration.class.php there is the following code:

$finder = sfFinder::type('dir')->maxdepth(0)->ignore_version_control(false)->follow_link()->name('*Plugin');

This line is forcing you, when you create a private plug-in, to append the word Plugin to your actual plugin name (directory) as mentioned above in the “good” example. I am not sure why the Symfony devs are forcing us to append Plugin when the directory is clearly namespacing plugins for us.

Hopefully this helps anyone else who had to jump down this rabbit hole.

Symfony – Standard API for Logging

5

As a developer you probably already know how important the concept of logging is. Logging greatly reduces the time it takes to debug defects which means you can spend more time developing fun stuff like new features. The following is a simple tip to assist in your debugging efforts.

According to symfony, you can access the logging framework through 1 of 3 of the following ways:

When inside an actions.class.php


$this->logMessage($level, $message);

// Example

$this->logMessage('err', 'Onoes something went wrong');

When inside a template:

<?php use_helper('Debug') ?>
<?php log_message($message, $level) ?>

// Example
<?php log_message('Ohnoes something went wrong', 'err') ?>

Anywhere else within Symfony:


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

// Example

sfContext::getInstance()->getLogger()->err('Ohnoes something went wrong');

You can create a unified API which can be called from anywhere, and has made my debugging life a little easier, consider the following:


// located @ /lib/MyCustom/Api/Log.php

class MyCustom_Api_Log
{
  /**
   * Wrapper method for getting a symfony logger object
   *
   * @return object
   */
  public static function getLogger()
  {
    return sfContext::getInstance()->getLogger();
  }

  /**
   * Wrapper method for logging debug message
   *
   * @param string $message
   */
  public static function logDebug($message)
  {
    self::getLogger()->debug($message);
  }

  /**
   * Wrapper method for logging an error
   *
   * @param string $message
   */
  public static function logError($message)
  {
    self::getLogger()->err($message);
  }

}

// Example Usage

MyCustom_Api_Log::logError('Ohnoes something went wrong');

Now, you can simply call this log wrapper class from anywhere within symfony and it will add an error level log message to the symfony log file.

One thing you may have noticed is the /lib/MyCustom directory, this will be explained in an upcoming post, but I have found that if you need to create classes that are not model related, putting them within in this directory keeps your /lib directory nice and clean and makes finding files much easier.

Symfony 1.4 – Rename Application

0

If you ever need to rename an application you can follow the directions given here: http://fabien.potencier.org/article/22/a-symfony-tip-rename-a-symfony-application. The directions work well for Symfony version 1.2 but if you are running 1.3+ you will also need to follow these extra steps:

Rename Config File:

 app/app_name/config/<old_app_name>Configuration.class.php TO app/app_name/config/<new_app_name>Configuration.class.php

Edit Config File:

// app/app_name/config/<new_app_name>Configuration.class.php
class <new_app_name>Configuration extends sfApplicationConfiguration

Now just clear cache and load your site through the new controller (new_app_name.php) and it should work as expected.

Symfony – Autoload.yml – Basic Usage

21

Autoloading is the term used to describe the process where class files are loaded at the first http request (and cached for subsequent requests). The main benefit of autoloading is that  you don’t need to add a bunch of ‘require_once’ calls to your classes to get things working, they are all autoloaded for you. Some may see this autoloading as ‘bloat’, but if strategically cached, it does allow for easier coding within the symfony framework.

Chances are you have some custom classes which are specific to your actual business needs. Rather than stuffing these custom classes in the core symfony directories (lib/model, lib/forms, lib/filters), which they don’t belong, you can create custom directories and then tell symfony about them so symfony can autoload them as if they were the core symfony directories.

Check out the following example:


custom_app: #Unique identifier for autoloading anything in given dir

  name:      Custom app classes #Something that makes sense to you

  ext:       php #You can load files with specific extensions

  path:      %SF_LIB_DIR%/app #The directory from which symfony should autoload files, in this case lib/app

  recursive: true #Whether to load files within nested dirs

As you can see, autoloading in symfony allows for flexible lib structures, which allows you to better structure your classes for faster development and easier maintenance.

Edit (2.16.11)

Thanks to Tix comment, you want to add autoload.yml files to /apps/<app_name>/config/autoload.yml

Edit (9.17.11)

Thanks to German Rumm’s comment, be sure that autoload: is the first line in your autoload.yml file.

Also, here is an actual example:

autoload:
  sfcustom_api:
    name:      Symfony specific custom apis
    ext:       php 
    path:      %SF_LIB_DIR%/MyCustom/api
    recursive: true

Symfony – sfWidgetFormDoctrineChoice vs sfWidgetFormChoice – When to Use Which

1

Seven months ago I started working with Doctrine (vs. Propel) and missed some of the benefits Doctrine provides,  due to lack of experience working with it. Over time I finally picked up on some of the benefits of doctrine and one of these benefits relates to the differences between sfWidgetFormDoctrineChoice and sfWidgetFormChoice (and their associated validators). This article is intended to demonstrate these differences.

sfWidgetFormDoctrineChoice

The main benefit of using this widget vs sfWidgetFormChoice, is that you can specify an object from your model and Doctrine will automagically know how to retreive the data. You can also specify other options to fully configure your choice options.  For example:

// lib/doctrine/form/BookForm.class.php
public function configure()
{
// This will create a single select drop downbox using data stored in Category model. It will also add an empty select option as the first option and sort the categories
// by name, asc
$this->widgetSchema['category_list']    = new sfWidgetFormDoctrineChoice(array('model' => 'Category', 'multiple' => false, 'add_empty' => true, 'order_by' => array('name', 'asc')));

// Or maybe you want a multiple select option, no problem.

// Notice the multiple option is set to true and the add_empty option has been removed, useful for admin double lists
$this->widgetSchema['category_list']    = new sfWidgetFormDoctrineChoice(array('model' => 'Category', 'multiple' => true, 'order_by' => array('name', 'asc')));
}

There are other options you can pass to sfWidgetFormDoctrineChoice which can be found via Symfony’s api documentation.

If you are a propel user, don’t fret, propel follows the same logic but uses sfWidgetFormPropelChoice.

Ok so we got sfWidgetFormDoctrineChoice setup, but what about an accompanying validator? Again, no problem, check out the following example:

// Notice that sfValidatorDoctrineChoice takes the same two arguments that we had in sfWidgetFormDoctrineChoice; model and multiple
// The only difference is the 'column' option which allows us to tell doctrine which column from the Category model will be used for validating
// submitted values
$this->validatorSchema['category_list']     = new sfValidatorDoctrineChoice(array('model' => 'Category', 'column' => 'id', 'multiple' => true));

Also, just like sfWidgetFormDoctrineChoice, there are other options  you can use when instantiating a new sfValidatorDoctrineChoice object; refer to the for all the options: api documentation.

So far I’ve only discussed sfWidgetFormDoctrineChoice, but what about sfWidgetFormChoice, when should we use it? I’ve found the best scenarios for using sfWidgetFormChoice occur when you have a list of options that are not supplied by a model object. For example, say you wanted to maintain a list of static options for book types; ‘format’ (i.e. paperback, hardcover).

Because there are only a handful of options which won’t change much over time, its better to make ‘format’ options class constants rather than create a table, add them to schema, and rebuild the model.

To display these options as a select list you would use sfWidgetFormChoice b/c there is no model which will automagically provide the needed data for your select list.

Lets see it in action:

// An example of using class constants to set values

// lib/model/doctrine/BookFormat.class.php
const FORMAT_TYPE_PAPERBACK = 1;
const FORMAT_TYPE_HARDCOVER = 2;

public static $formatTypes = array(
    self::FORMAT_TYPE_PAPERBACK => 'paperback',
    self::FORMAT_TYPE_HARDCOVER => 'hardcover'
);

// lib/form/doctrine/BookForm.class.php

$formatTypes = BookFormat::$formatTypes;

// I want to add an empty element so user is aware they must select something
$formatTypes = array('' => '') + $formatTypes

// Now pass to sfWidgetFormChoice
$this->widgetSchema['book_formats'] = new sfWidgetFormChoice(array('choices' => $formatTypes));

// Notice we calling array_keys so when form validates it will validate against 1,2,3 vs the string (paperback, hardcover)
$this->validatorSchema['book_formats'] = new sfValidatorChoice(array('choices' => array_keys($formatTypes), 'required' => false));

One thing to remember with the sfWidgetFormChoice example, you still must have a column in your object model to house the submitted value, otherwise the submitted value will be redirected to /dev/null.

Hopefully this article clearly demonstrated the differences between sfWidgetFormDoctrineChoice and sfWidgetFormChoice and when to use which. Thanks for reading and HTH.

Symfony – Saving Metadata During Form Save (sort ids)

30

A few months ago I wrote an article about how to edit the sfFormExtraPlugin to respect metadata for ‘associated’ (selected) items. Back then I was able to get this to work ‘out-of-the-box’ because MySQL was playing nicely, but after a few weeks I found that the sort order wasn’t being respected (something arbitrary about mysql’s uuid).

Being that I don’t care much for ‘unexplained’ behavior I decided to force the a sort order by adding a new ‘sort_id’ (metadata) column on the linking table (LinkingAuthorBook). Now with the schema ready to support the saving of sort_id, the trick is how to save the sort_id during the form save process, which is what this article will demonstrate.

This article was written for use with Symfony 1.4, but it shouldn’t be a problem applying it to older versions as well.

We will be using the schema outlined here as a basis for this article, so be sure you have your schema in place and model up-to-date before continuing.  If you are following this article verbatim you will be making these changes to lib/form/doctrine/AuthorForm.class.php, otherwise if you are using an existing codebase, you will need to decide where the following changes will be made.

This article assumes the importance of saving books linked to an author in chronological order, which is to demonstrate the importance of maintaining an order, how you implement the following ides within your own projects is entirely up to you. Also, I tried to make the following ideas as simple as possible, so hopefully they aren’t too hard to understand.

Override doSave()

Pretty much a verbatim cut and paste from the base class (lib/form/doctrine/base/BaseLinkingAuthorBook.class.php), so we can add our own custom functions. Note that I am calling on the parent’s parent method vs the parent method because we don’t want to do the work twice.

//lib/form/doctrine/AuthorForm.class.php

protected function doSave($con = null)
{
    $this->saveBookList($con); //Default - Found in BaseLinkingAuthorBook.class.php

    BaseFormDoctrine::doSave($con); //Notice we aren't calling parent::doSave()

    LinkingAuthorBook::saveSortIds($this->getObject()->getId(), $this->getLinkingAuthorBookSortIds()); //Why we overrode, this is our custom stuff
}

Override updateDefaultsFromObject()

This method retrieves the default values saved to an existing object, by overriding it we can dictate how we want the data pulled. By default the data is  retrieved via ‘getPrimaryKeys()’, if you notice below we are using a custom getter instead.

//lib/form/doctrine/AuthorForm.class.php

public function updateDefaultsFromObject()
{
    BaseFormDoctrine::updateDefaultsFromObject(); //Notice, no call to parent::updateDefaultsFromObject()

    if (isset($this->widgetSchema['book_list']))
    {
        $this->setDefault('book_list', LinkingAuthorBook::getPrimaryKeysByAuthorIdOrderBySortId($this->object->getId()); //We are using a custom getter vs the default $this->object->Book->getPrimaryKeys()
    }
}

Add custom primary keys getter

The following method will return primary keys based ordered by sort_id (asc), which is used by updateDefaultsFromObject().

//lib/model/doctrine/LinkingAuthorBook.class.php

public static function getPrimaryKeysByAuthorIdOrderBySortId($authorId)
{
    $primaryKeys = array();

    $q =
        Doctrine_Query::create()
            ->from('LinkingAuthorBook link')
            ->where('link.author_id = ?', array($authorId))
            ->orderBy('link.sort_id');

    $results = $q->execute();

    foreach ($results as $result) {
        $primaryKeys[] = $result->getBookId();
    }

    return $primaryKeys;
}

Override saveBookList()

This is a verbatim copy and paste from the base class except for the section marked as ‘Sort Ids’. Be sure you add it exactly as shown below. DO NOT COPY the following code verbatim, the code before and after the ‘Sort Ids’ block is there for reference only.

//lib/form/doctrine/AuthorForm.class.php

public function saveBookList($con = null)
{
    if (!is_array($values))
    {
        $values = array();
    }

    //-----
    //Sort Ids
    //-----
    $count = 1;

    foreach ($values as $value)
    {
        $sortIds[$value] = $count++;
    }

    $this->setLinkingAuthorBookSortIds($sortIds); //The reason for a custom member, setter, and getter
    //-----

    $unlink = array_diff($existing, $values);
}

Add member var, setter and getter

Using member var, setter and getter will allow us to save the sortids when the saveBookList() method is called, which we can reference later (during doSave()).

//lib/form/doctrine/AuthorForm.class.php

/**
 * This var will store linking book author sort ids
 *     Spelled out in case order of entry is critical for other linking stuff
 *
 * @var array
 */
 protected $linkingAuthorBookSortIds;

 /**
 * Setter for linking drink instruction sort ids
 *
 * @param array $linkingAuthorBookSortIds
 */
 public function setLinkingAuthorBookSortIds($sortIds)
 {
     $this->linkingAuthorBookSortIds = $sortIds;
 }

 /**
 * Getter for linking drink instruction sort ids
 *
 * @return array
 */
 public function getLinkingAuthorBookSortIds()
 {
     return $this->linkingAuthorBookSortIds;
 }

Make changes to sfFormExtraPlugin

Follow the instructions outlined in the following article. Basically you will need to make some changes to the render() method to ensure it respects the order in which you retrieved ‘associated’ values (the reason why we overrode updateDefaultsFromObject()).

Custom method to update rows

You want to add this method to your lib/model/doctrine/LinkingAuthorBook.class.php file. This is the method that will actually update the rows with their corresponding sort_ids.

//lib/model/doctrine/LinkingAuthorBook.class.php

public static function saveSortIds($authorId, $sortIds)
{
    if (empty($authorId))
    {
        throw new Exception('Invalid authorId');
    }

    elseif (empty($sortIds))
    {
        throw new Exception('Invalid sortIds');
    }

    $q =
        Doctrine_Query::create()
            ->update('LinkingAuthorBook link');

    foreach ($sortIds as $bookId => $sortId)
    {
        $q
            ->set('link.sort_id', $sortId)
            ->where('link.author_id = ?', array($authorId))
            ->andWhere('link.book_id = ?', array($bookId));

        $q->execute();
    }
}

Conclusion

At this point you should be noticing that rows in your linking table are being saved with sort_id, which are then displayed in correct order  in the ‘associated’ section of your double lists. If not, check the changes you made, the first thing I was looked into was to be sure the data was being saved in the database correctly. If they aren’t, check the changes you made to the AuthorForm.class.php and LinkingAuthorBook.class.php files.

Hope this helped!

Symfony – Upgrade from 1.2 to 1.3 – Filters.yml

1

If you are upgrading from version 1.2 to version 1.3 of symfony you may run into some odd filter issues after following their online guide @ http://www.symfony-project.org/tutorial/1_4/en/upgrade.

The main problem I had with this upgrade was the syntax construct for filters.yml has completely changed. This was a struggle b/c there was no mention of the change in the upgrade guide and I obviously didn’t want to completely replace my project files (nor did the guide say I had to). After some digging around I found the following link; http://www.symfony-project.org/reference/1_4/en/12-Filters, which demonstrates how the filters.yml file should be constructed. After following the new syntax construct example, I was able to successfully load my site.

Seems odd that the symfony devs don’t acknowledge this change on their ‘how to upgrade’ page.

Symfony – Push to Production Checklist

0

There is a great site out there that offers up a checklist you should conduct before launching your symfony project to production, check out the following link:

http://symfony-check.org/

One cool note that is definitely worth mentioning is the “Display an unavailable message during maintenance operations” check. Basically you can configure a setting in your settings.yml file which will redirect a user to an ‘unavailable’ page during a ‘symfony cc’. This will avoid the problem where some users may get white pages during your publish process, and will also ensure that incomplete template caches are no longer a problem.

Go to Top