Symfony
Symfony – Standard API for Logging Using __callStatic
0Last year I wrote an article on how to write a simple, standard logging class for Symfony (http://melikedev.com/2010/08/25/symfony-standard-api-for-logging/). This article will demonstrate how do to the same functionality in only a few lines of code using __callStatic(). For more info on __callStatic() check out http://melikedev.com/2011/10/31/php-__callstatic-method-overloading/
Before:
// 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('Oh noes something went wrong');
After:
// 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();
}
/**
* Magic method for logging all supported log levels
*
* @param string
* @param array
*/
public static function __callStatic($level, $params)
{
// Only concerned with message at this time
// However any number of params can be passed in
$message = shift($params);
self::getLogger()->$level($message);
}
}
// All these calls are made to __callStatic, so no need for individual methods
MyCustom_Api_Log::err(array('Oh noes something went wrong'));
MyCustom_Api_Log::warn(array('Oh noes something went wrong'));
MyCustom_Api_Log::notice(array('Oh noes something went wrong'));
Symfony – Symfony 2 Security Audit Results
0It appears Fabien Potencier (lead engineer for the Symfony Framework) enlisted the help of an outside firm to audit the security of the Symfony 2 code-base. He posted the results a few days ago @ http://symfony.com/blog/symfony2-security-audit. Overall, it looks like the Symfony 2 code-base made the grade with regards to security issues. I did pose the question if they conducted the same type of audit for the Symfony 1.4 codebase as it will still be officially supported until 2013. If/when I find out I will update this post.
I applaud his efforts to provide a robust and secure framework.
Symfony – Serve External Apis from External Hosts – jQuery – Yui
0As a developer you have probably had to use a 3rd party API library to make your job easier. Take for example jQuery. jQuery abstracts many internal javascript constructs which make calling code much easier to work with, and jQuery works the same across most standard web browsers. Which brings me to the point of this post, what is the best method of bringing these 3rd party API libraries into your projects? Sure you could download the files and serve them from your own web-servers, but why? Why not let 3rd party API hosts such as Google or Yahoo, serve these API libraries for you?
Consider the following links:
Typical host construct: http://static.yourdomain.com/js/vendor/jquery/jquery.min.js
3rd party API host construct: http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js
What’s the difference between the two files? Nothing (given that you abstracted out the version number using a symlink in the first example). However, with the 3rd party API host you are using their resources vs using your own resources. So that means more http connections for serving actual content rather than serving API libraries. Also, serving API libraries from these external hosts also ensures faster delivery of the data because there is a high probability that the 3rd party host is using a CDN (or cloud) to serve the request.
Now we see why it’s better to use 3rd party API hosts to serve common API libraries, but how do we configure Symfony to serve these API libraries. We don’t want to litter our templates with url constructs, so we are going to add some code to our top level app.yml and view.yml files. If you are unaware, a flexible feature of using yml files vs xml or ini is that with yml files you can add php code, which will be executed when the yml files are parsed.
First, lets set our 3rd party API host, and jquery version via our app.yml file. Note: You can set these same values at the global config level (/config/app.yml vs /apps/<app_name>/config/app.yml) so all your apps will benefit.
[yml]
#/apps/<app_name>/config/app.yml
all:
jquery_api_version: 1.6.2
google_ajax_libs_api_uri: http://ajax.googleapis.com/ajax/libs
[/yml]
Ok, now lets configure our view.yml file. Same note as above, this can be set at the global config level so all your apps will benefit.
#/apps/<app_name>/config/view.yml
default:
javascripts:
- <?php echo sfConfig::get('app_google_ajax_libs_api_uri') ?>/jquery/<?php echo sfConfig::get('app_jquery_api_version') ?>/jquery.min.js
- <?php echo sfConfig::get('app_google_ajax_libs_api_uri') ?>/jqueryui/<?php echo sfConfig::get('app_jquery_ui_api_version') ?>/jquery-ui.min.js
Pretty easy right? Notice the – (hyphen) which starts the line, this is a flag to yml parsers that data within current tag will be an array. I found this syntax much easier to deal with vs having several entries on the same line. Also, check out the php echos, they are simply referring to settings we initialized in our app.yml file.
Not only does this solution allow you to serve 3rd party API libraries from 3rd party hosts, you can now upgrade all API libraries with a few minor changes in a centralized location.
Symfony – sfGuardPlugin – Use Email Instead of Username
6sfGuardPlugin is pretty awesome. It allows a symfony developer the ability to quickly implement a user login and access control system. However, there is an issue with respect to telling the plugin whether to use the username or the email column for validating user submitted input. After some Googling I found a few sites which forced sfGuardPlugin to use email rather than username, but only after quite a bit of work. What I am going to show will accomplish the same end goal, but with minor changes.
First, lets take a look at the calling code that determines how the user submitted login information will be validated.
// plugins/sfDoctrineGuardPlugin/lib/validator/sfGuardValidatorUser.class.php
protected function doClean($values)
{
$username = isset($values[$this->getOption('username_field')]) ? $values[$this->getOption('username_field')] : '';
$password = isset($values[$this->getOption('password_field')]) ? $values[$this->getOption('password_field')] : '';
$allowEmail = sfConfig::get('app_sf_guard_plugin_allow_login_with_email', true);
$method = $allowEmail ? 'retrieveByUsernameOrEmailAddress' : 'retrieveByUsername';
// don't allow to sign in with an empty username
if ($username)
{
if ($callable = sfConfig::get('app_sf_guard_plugin_retrieve_by_username_callable'))
{
$user = call_user_func_array($callable, array($username));
} else {
$user = $this->getTable()->retrieveByUsername($username);
}
// user exists?
if($user)
{
// password is ok?
if ($user->getIsActive() && $user->checkPassword($password))
{
return array_merge($values, array('user' => $user));
}
}
}
if ($this->getOption('throw_global_error'))
{
throw new sfValidatorError($this, 'invalid');
}
throw new sfValidatorErrorSchema($this, array($this->getOption('username_field') => new sfValidatorError($this, 'invalid')));
}
We are not going to make changes to this code because it’s core code and would be overridden every time you update sfGuardPlugin, rather we are just going to dissect it a bit to see what changes we need to make to another file, and why.
line 08: The code is looking for ‘allow_login_with_email' within our app.yml file, and if it doesn't exist, defaults to true.
line 09: What is the purpose of this line? $method is being set, but is never used. This should be removed in a future iteration.
line 14: Apparently there is the flexibility to define your own callback function by defining retrieve_by_username_callable within our app.yaml file, but if you look at line 16, it forces us to use a function rather than a method.
line 18: Finally, the magic method which determines which field will be used in determining a user’s login information (email or username). But notice the problem here? The code is calling getTable()->retrieveByUsername($username), as far as I know, table classes are supposed to be containers for static methods which help us interface with their instantiated object counterparts. Whatever, we are going to overlook this.
Ok, now for the change YOU need to make to use email rather than username.
// lib/model/doctrine/sfDoctrineGuardPlugin/sfGuardUserTable.class.php
class sfGuardUserTable extends PluginsGuardUserTable
{
....
public function retrieveByUsername($username, $isActive = true)
{
$query = self::getInstance()
->createQuery('u')
->where('u.email_address = ?', $username)
->addWhere('u.is_active = ?', $isActive);
return $query->fetchOne();
}
....
}
Now clear cache, and try to login with an email address instead of an username. With any luck it should work as expected. Sweet right? How about if you want to check both username AND email? No prob, consider the following:
public function retrieveByUsername($username, $isActive = true)
{
$query = self::getInstance()
->createQuery('u')
->where('u.email_address = ?', $username)
->orWhere(‘u.username = ?’, $username)
->addWhere('u.is_active = ?', $isActive);
return $query->fetchOne();
}
Notice the ‘orWhere’ addition, this will allow sfGuardPlugin to check either the email column OR the username column.
So that’s it, hopefully this seemingly easily feature in theory, however difficult to understand in practice, worked for you.
Symfony – PHP – Possibly Forking 1.x so Invested Companies don’t Lose Millions
13So there seems to be some rumblings from within the Symfony world that the dev team “may” fork 1.x in order to continue maintenance beyond the announced 2013 “end of life” window, rather than ending active development on 1.x and “hoping” everyone shifts to Symfony 2.
Source blog post @ http://test.ical.ly/2011/06/22/quo-vadis-symfony-1-4/ (lol @ FQDN).
I posted a comment on the blog, which is copied verbatim here in case it gets lost in the static.
It’s amazing that we are even having this type of discussion. We opted to use Symfony back in 0.x for some high traffic projects. Our company spent a lot of time and money training the dev team to correctly use the framework. And it wasn’t just the initial learning of moving from a hodge-podge code-base to MVC code-base, we also had to relearn new ways of doing things with some minor revisions (1.0 -> 1.2 forms for example). How much time was spent learning how to get the form system to work with some highly customized forms? A lot.
And now here we are on 1.4. Content and happy with the throughput the dev team is able to maintain. But now Symonfy says that 2.0 will be released and is completely different than 1.x? How is that fair to the companies, individuals, and hobbyists who have sunk time over the last four years ramping up to a point where we can take advantage of RAD (rapid app development) approach that Symfony provides? Granted, Symfony did extend the maintenance window of 1.x to 2013, but to completely abandon a great framework is callous and irresponsible, to be frank.
When 1.x is no longer supported, the dev team will just have to maintain 1.x as if it were our own. It’s tragic, but financially, we can’t keep paying the costs of heavy refactors incurred with new major revisions. Will we be having this discussion again in three to four years, when Symfony 3 is slated for release? When companies during that time period would have made the brutal choice to shift from Symfony 1 to Symfony 2, only to be abandoned again?
IMO, I think Symfony should follow closer to the examples set by industry giants such as Apache, MySQL, and PHP with regards to major revisions. Make them available, but be gentle about the transition. Leave it up to the companies to determine whether or not to upgrade.
Thanks and I <3 Symfony.
My question is, what do other Symfonians think of the upcoming changes?
Symfony – Remove .php From Controller using Symlink
1Symfony 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)
0The 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):
[yaml]
javascripts: [http://www.google.com/recaptcha/api/js/recaptcha_ajax.js]
[/yaml]
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
0If 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
5As 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
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.
