Symfony
Symfony 1.4 – Rename Application
0If 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: app/<app_name>/config/<old_app_name>Configuration.class.php TO app/<app_name>/config/<new_app_name>Configuration.class.php
Edit: app/<app_name>/config/<new_app_name>Configuration.class.php to:
class <new_app_name>Configuration extends sfApplicationConfiguartion
Now just clear cache and load your site through the new controller and it should work as expected.
Symfony – Autoload.yml – Basic Usage
21Autoloading 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
1Seven 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)
29A 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
1If 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
0There 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.
Symfony – Adjust Widget Order
7If you are using the Symfony form framework you may have experienced issues with the order of widgets as they appear on the form. For example say you have a user table with the following columns: first_name, last_name, and dob (date of birth), and you execute the symfony ‘build-forms’ command and view your form you will see that the dob widget is in between the first_name and last_name widgets. You can change the order in the base form file in the /base form directory, but the change would be overwritten everytime you re-run the symfony ‘build-forms’ command, instead try the following:
//UserForm.class.php
public function configure()
{
$this->getWidgetSchema()->moveField('dob', sfWidgetFormSchema::AFTER, 'last_name');
}
Symfony – Override Doctrine Methods
0Ran into a situation where I had to override the Doctrine Record::Link() method and was having a tough time trying to figure out the best approach. A few minutes googling and came across the following article: http://snippets.symfony-project.org/snippet/373 .
It goes over how to add your custom (override) class to the chain of extends within the symfony framework. The result? A greater control of low level methods.
Symfony – Accessessing Unescaped Multi-dimensional Array Values in Templates
2A majority of the time, symfony templates are iterating through arrays set by a certain action in their respective actions class and if you have ESC_SPECIALCHARS set to ‘ON’ in your view.yml file, all the string values will have special chars converted from string values to html entities. For example a space looks like ‘ ‘ via render, but if you view html source you will see ‘ ’.
Escaping output is always a good idea, especially if you are allowing users to post comments (etc.) to your website. Escaping html entites ensures that any html they enter will not be rendered as html, which may wreck havoc with your site.
As great as escaping output is, there are times when you need to access raw values (unescaped). Let me give you an example. Say you have an multi-dimensional array like; $blogs[0]['blogName'] which stores ‘Mike’s Dev Blog’. As you can see, the special char here is the apostrophe in Mike’s. In the symfony template if you echo $blogs[0]['blogName'] and view html source you will see Mike's Dev Blog. & is the html entity for an apostrophe.
Why would you ever want to access the unescaped version of Mike’s Dev Blog? Simple, say we want to build a link using Mike’s Dev Blog as a querystring parameter. For example: http://melikedev.com/article?id=Mike’s Dev Blog . What’s wrong with this picture? Spaces, making it an invalid url, so we need to do a urlencode (or rawurlencode) so then the link looks like: http://melikedev.com/article?id=Mike’s+Dev+Blog, which works fine. The problem lies in the fact that accessing the value Mike’s Dev Blog within a symfony template can be tricky, consider the following example (template file):
foreach ($blogs as $blog):
echo $blog['blogName']; //Displays Mike's Dev Blog in source html
echo $blog->unescape($blog->get('blogName')); //Displays Mike's Dev Blog in source html
echo urlencode($blog->unescape($blog->get('blogName'))); //Displays Mike's+Dev+Blog in source html
endforeach;
As you can see in the above example we were able to access $blogs[0]['blogName'] which is a multi-dimensional array.
It took some time to figure out b/c I was so used to simply doing $sf_data->getRaw(‘someVariable’), but this only works if you set $this->someVariable = ‘someValue’ in the actions class.
Hope this helps.
Symfony – Access Current Route
0If you need access to routing data via your actions.class.php file (can also be used in template files), use the following:
sfContext::getInstance()->getRouting()->getCurrentRouteName()
