By using this technique you can have exact copies of an application without duplicating a single file!
Step 1 - Set up the subdomain
Create a new subdomain in the DNS and configure apache to accept it as an alias for the live site.
<VirtualHost *:80>
ServerName site.com
ServerAlias www.site.com demo.site.com
DocumentRoot "/var/www/"
</VirtualHost>
Step 2 - Create a new database for the subdomain and configure the databases.yml file
test:
propel:
class: sfPropelDatabase
param:
dsn: mysql://root:pass@localhost/live
demo:
propel:
class: sfPropelDatabase
param:
dsn: mysql://root:pass@localhost/demo
Step 3 - Modify the controller
Symfony 1.0:
//check for DEMO site
$env = "live";
$debug = false;
$domain = $_SERVER['HTTP_HOST'];
if (strpos($domain,"demo") !== false){
$env = "demo";
$debug = true;
}
define('SF_ROOT_DIR', realpath(dirname(__FILE__).'/../..'));
define('SF_APP', 'my_app');
define('SF_ENVIRONMENT', $env);
define('SF_DEBUG', $debug);
Or in Symfony 1.2:
$configuration = ProjectConfiguration::getApplicationConfiguration('my_app', $env, $debug);
sfContext::createInstance($configuration)->dispatch();
Step 4 - Create the filter
My favourite bit - save it in your lib folder:
<?php
class demoFilter extends sfFilter
{
public function execute ($filterChain)
{
if (SF_ENVIRONMENT == "demo" && $this->isFirstCall())
{
$this->getContext()->getResponse()->addStylesheet('demo','last');
}
// execute next filter
$filterChain->execute();
}
}
?>
Step 5 - Add your stylesheet & images etc
The above filter adds demo.css to each page, so create the file and any images it requires and upload to your existing css and images folders and you're done.
| [ 0 trackbacks ] | related link
I recently set up mySQL replication for a client and thought I'd share this quick and easy way of distributing database load over a few machines.
First, set up replication - it's very simple. Here's a simplified list of tasks based on the info at mysql.com:
http://forums.gentoo.org/viewtopic.php?t=241123
Then you need to adapt your site to use the master server for all write operations and distribute the read load across your slaves (and master if you like).
My setup was moving from a single server to 2 servers. The primary server runs apache and the master database. The secondary server takes all the database read traffic (high CPU low bandwidth) and also serves up media files (low CPU high bandwidth).
The way I set up the config for the databases was to allow for failover from the slave back onto the master. Now this site was built in 2003 and uses some very old PEAR classes (remember PEAR? ;-), but you get the idea:
$options = array(
'debug' => 2,
'portability' => DB_PORTABILITY_ALL,
);
$dsn = array(
'phptype' => 'mysql',
'username' => def_user,
'password' => def_pass,
'hostspec' => def_host,
'database' => def_dbname,
);
$db_master =& DB::connect($dsn, $options);
if (PEAR::isError($db_master)) {
showError($db_master->getMessage());
}
$db_master->setFetchMode(DB_FETCHMODE_OBJECT);
//use 2nd database server if available
//otherwise duplicate original as failover
if (defined('def_host2')){
$dsn2 = array(
'phptype' => 'mysql',
'username' => def_user2,
'password' => def_pass2,
'hostspec' => def_host2,
'database' => def_dbname2,
);
$db =& DB::connect($dsn2, $options);
if (PEAR::isError($db)) {
$db = $db_master;
}else{
$db->setFetchMode(DB_FETCHMODE_OBJECT);
}
}else{
$db = $db_master;
}
Then the next step was to modify the pages that included DELETE, UPDATE or INSERT queries to use $db_master instead of $db. The whole process took a little over an hour and the site was only offline for 20 minutes :)
| [ 0 trackbacks ] | related link

How to add a Symfony command to PhpEd:
1. Menu: Tools->Settings
2. Go to Tools -> Integration
3. Click "Add Menu"
4. Enter "Symfony cc" in menu name
5. Select "Shell" from Execute with list
6. Enter php @ProjRoot@/symfony cc in command line
7. Select checkbox next to "Show this command in Workspace" group
8. Deselect "for files" checkbox
9. Select "for directories and projects"
Save and then try right-clicking on a project to run your command from the context menu.
You can also direct the output of the command line back into PhpEd's log panel - this is a good way to ensure your CLI commands are executing without errors - as the windows command prompt has a tendency to flash and disappear before your eyes...
| [ 0 trackbacks ] | related link
This has come in handy for a couple of mail queues I have built and thought it was worth sharing. Basically it's a small check for PHP in the running system processes. If *any* PHP script is running (besides itself) we abort the script. It would be nice to know *which* script was running but I haven't managed to do that yet :)
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
//windows, can't do thread detection
$threads = 1;
}else{
//check for PHP PID and abort
$pids = preg_split('/\s+/', `ps -o pid --no-heading -C php`);
$threads = 0;
foreach($pids as $pid) {
if(is_numeric($pid)) {
$threads++;
echo "Process " . $pid." found\r\n";
}
}
}| [ 0 trackbacks ] | related link
Sometimes I wonder about using mime types to validate file types. Usually using file extensions is more reliable seeing as though eah browser uses its own set of mime types fore each file extension anyway.
So here I am again modifying some core Symfony files to fix browser mime type inconsistencies, this time it's IE7 playing up. I mean honestly what's with image/x-png as a mime type?
But if you want to generate sfThumbnails you'll need to modify the sfGDAdapter class:
/**
* List of accepted image types based on MIME
* descriptions that this adapter supports
*/
protected $imgTypes = array(
'image/jpeg',
'image/pjpeg',
'image/png',
'image/x-png',
'image/gif',
);
/**
* Stores function names for each image type
*/
protected $imgLoaders = array(
'image/jpeg' => 'imagecreatefromjpeg',
'image/pjpeg' => 'imagecreatefromjpeg',
'image/png' => 'imagecreatefrompng',
'image/x-png' => 'imagecreatefrompng',
'image/gif' => 'imagecreatefromgif',
);
/**
* Stores function names for each image type
*/
protected $imgCreators = array(
'image/jpeg' => 'imagejpeg',
'image/pjpeg' => 'imagejpeg',
'image/png' => 'imagepng',
'image/x-png' => 'imagepng',
'image/gif' => 'imagegif',
);
| [ 0 trackbacks ] | related link
It's rare that I feel the need to edit core symfony files, but this is one situation where it is the best choice. Symfony has a database of mime types it references when processing file uploads. It is pretty comprehensive, but the entries for ZIP files are:
application/x-zip-compressed
application/zip
To get Firefox uploads to work you need to add this:
application/x-zip
You can do this by adding it to the mime type array on the fly:
$mimeTypes['application/x-zip'] = 'zip';
Or even better, update the mime_types.dat file by adding this:
s:17:"application/x-zip";s:3:"zip";
And don't forget to increment the array count at the beginning of the file:
a:406:
| [ 0 trackbacks ] | related link
AT first I thought I had found a bug in the way symfony decorates templates with layouts. But as usual, I discovered a bug in my own code :) Hopefully this will save someone else from having to debug a situation which turned out to be issue with using PHP's output buffer within a symfony action.
The problem: One of my actions was being rendered to the browser with the layout code (ie: html, head and body tags etc) in the middle of my template.
After debugging I discovered that symfony uses the output buffer to render templates, so if you mess with the buffer then you mess with the way your template gets rendered.
The renderFile() method if sfPHPView.class reads templates like this:
ob_start();
ob_implicit_flush(0);
require($_sfFile);
return ob_get_clean();
I had a utility function that was using the buffer...
ob_start();
//do stuff with the buffer
What I didn't realise was that Symfony had already called ob_start(), so my ob_start() was erasing the buffer and only sending output from that point onwards back to the render function.
The solution: If you ever need to capture the output buffer in Symfony, make sure you save the existing buffer and then render it back out when you are done.
My final code looked like this:
$buffer = ob_get_clean();
ob_start();
//do stuff with the buffer
ob_clean();
echo $buffer;
OK it's pretty simple, but diagnosing it was for from simple!
| [ 0 trackbacks ] | related link
Coming from a Prototype background I found extJS infuriating for some really basic tasks like this: calling a remote function and evaluating the response. In Prototype you just set evalScripts parameter to TRUE in the ajax call, but in extJS you have to jump through a few hoops.
The alternative is to use an updater, where you can set the scripts param to true, much like prototype. But if you're NOT updating a DOM element, then you have to do this:
Ext.Ajax.request({
url: '/module/action',
params: { id: my_id},
callback: function(options, success, response) {
var r = response.responseText;
if(window.execScript) {
window.execScript(r);
} else {
var global = this;
global.eval ? global.eval(r) : eval(r);
}
}
});It's a pain, but it's rock solid!
| [ 0 trackbacks ] | related link
Some projects need a staging site - somewhere where you can test and play around without messing up your database or webstats. Traditional applications require you to make a complete copy of your site to do this - and that means you need to sync your dev files with 2 remote locations.
With Symfony, for example, you can just point your staging domain or subdomain to the exact same location and set the environment on the fly.
This way you are using the exact same PHP files, but you have control over the usual environment settings such as database connection, application variables, email recipients etc etc.
In Symfony 1.2 you just modify your index.php controller like so:
<?php
##IP_CHECK##
require_once(dirname(__FILE__).'/../../config/ProjectConfiguration.class.php');
//check for DEMO site
$env = "live";
$debug = false;
$domain = $_SERVER['HTTP_HOST'];
if (strpos($domain,"demo") !== false){
$env = "demo";
$debug = true;
}
$configuration = ProjectConfiguration::getApplicationConfiguration('project_name', $env, $debug);
sfContext::createInstance($configuration)->dispatch();
Now just set your database in databases.yml
demo:
propel:
class: sfPropelDatabase
param:
dsn: mysql:dbname=demo_database;host=demo_server
username: demo_user
password: demo_pass
encoding: utf8
persistent: true
pooling: true
classname: DebugPDO
And finally, set your application settings in your app.yml
demo:
contact_email: contact@demo.com
#etc
If you want to make your staging/demo site appear differently, you can add a filter that inserts a stylesheet:
class demoFilter extends sfFilter
{
public function execute ($filterChain)
{
// execute this filter only once
$env = sfContext::getInstance()->getConfiguration()->getEnvironment();
if ($env = "demo" && $this->isFirstCall())
{
$this->getContext()->getResponse()->addStylesheet('demo','last');
}
// execute next filter
$filterChain->execute();
}
}
Note that the stylesheet should be set to 'last' to ensure you override your default styles.
You also need to add your filter to your filter.yml
rendering: ~
security: ~
demo:
class: demoFilter
#other filters go here
functional_test:
class: swFilterFunctionalTest
cache: ~
common: ~
execution: ~
The handy thing about using a filter is that you can apply it to multiple applications in the same project. Now you can set a background image in demo.css
body {
background-image: url(/images/demo.gif);
}
| [ 0 trackbacks ] | related link

Just got a press release from upstart IDE PHPEdit 3. They have built new support for Symfony, including a GUI for the CLI. It also has full YAML support and the ability to work intuitively with actions, controllers and templates. Just going to have to try this out!
| [ 0 trackbacks ] | related link
Next



Avatar



