![]() |
|
Snippets |
|
or apply transformation on sfGuard tables into your main schema
When you generate a schema with sf_guard tables from your main database with the following command, the schema contains all the tables included the sf_guard tables (it's normal).
symfony propel-build-schema xml
Then after, when you build the model classes, the sfGuardPlugin classes have a wrong name and are in a wrong place (lib/). There is a post on this problem, how to correctly use propel-build-schema after sfGuardPlugin installation?
symfony propel-build-model
To correct the schema, apply the transformation on the XML schema after the schema generation :
symfony transform-schema-sfguard xml
The script add in the schema for all sf_guard_* tables, the package and the phpName with the camelcase used by the plugin.
Also you have to switch off the schema in the plugin to don't have twice declaration :
mv ./plugins/sfGuardPlugin/config/schema.yml ./plugins/sfGuardPlugin/config/schema.yml.off
The script: myPakeTransformSchemaSfguard.php
pake_desc( 'apply transformation on sfGuard tables into your main schema' ); pake_task( 'transform-schema-sfguard', 'project_exists' ); function run_transform_schema_sfguard($task, $args) { // Check params // -- missing params ? if ( !count($args) > 1 ) { throw new Exception("You must provide a transformation to apply.\nsymfony transform-schema-sfguard\nsymfony transform-schema-sfguard xml"); } // -- schema exists ? if ($args[0] == 'xml') { $schema_filename = sprintf( '%s/schema.xml', sfConfig::get('sf_config_dir') ); if ( !file_exists($schema_filename) ) { throw new Exception( "Missing schema.xml" ); } } else { $schema_filename = sprintf( '%s/schema.yml', sfConfig::get('sf_config_dir') ); if ( !file_exists($schema_filename) ) { throw new Exception( "Missing schema.yml (not yet implemented)" ); } else { throw new Exception( "schema.yml not yet implemented" ); } } // Backup schema //pake_copy( $schema_filename, $schema_filename . '.previous', array('override' => true) ); if ($args[0] == 'xml') { $handle = fopen($schema_filename, "r"); // get the entire file in $contents $contents = ''; while (!feof($handle)) { $contents.= fread($handle, 8192); } fclose($handle); $num = preg_match_all('/<table(.*)>/i', $contents, $matches); // each table definition found foreach ($matches[0] as $val) { if (!preg_match('/package|phpName/i', $val) && preg_match('/name="(sf_guard_.+?)"/i', $val, $val_matches)) { $table_name = $val_matches[1]; //$php_name = sfInflector::camelize($val_matches[1]); $php_name = sfToolkit::pregtr($val_matches[1], array('#/(.?)#e' => "'::'.strtoupper('\\1')", '/(_)(.)/e' => "strtoupper('\\2')", '/(^)(.)/e' => "strtolower('\\2')")); $pattern = '/(<table.*)(name="'.$table_name.'")(.*>)/i'; $replace = '$1$2 package="plugins.sfGuardPlugin.lib.model" phpName="'.$php_name.'" $3'; $contents = preg_replace($pattern, $replace, $contents, 1, $count); pake_echo($table_name); } } // write the result $handle = fopen($schema_filename, "w+"); fwrite($handle, $contents); fclose($handle); } }
Put the myPakeTransformSchemaSfguard.php script into data/tasks directory
mv ./plugins/sfGuardPlugin/config/schema.yml ./plugins/sfGuardPlugin/config/schema.yml.off symfony propel-build-schema xml symfony transform-schema-sfguard xml symfony propel-build-model
Some PHP code to clean the cache very useful when you don't have access to symfony "command line interface" on production server.
file: symfony_cc.php
<?php function deltree($f) { $sf = realpath($sf); if (is_dir($f)) { foreach(glob($f.'/*') as $sf) { if (is_dir($sf) && !is_link($sf)) { deltree($sf); if (is_writable($sf)) { echo 'Delete dir.: '.$sf."\n"; rmdir($sf); } } else { if (is_writable($sf)) { echo 'Delete file: '.$sf."\n"; unlink($sf); } } } } else { die('Error: '.$f.' not a directory'); } } echo '<pre>'; echo 'Clean symfony cache'."\n"; echo "\n"; deltree('../../cache'); echo '</pre>';
I create a script to use complex arguments while runing script from command line.
It also allow to switch SF_ENVIRONMENT, SF_APP and SF_DEBUG directly from command line.
Just create configBatch.php in your project's config directory and paste this :
<?php define('SF_ROOT_DIR', realpath(dirname(__file__).'/..')); $argv = array(); for ($i = 1; $i < $_SERVER["argc"]; $i++) { if ($_SERVER["argv"][$i]{0} === '-') { // argument $value = ( isset($_SERVER["argv"][$i+1]) && $_SERVER["argv"][$i+1]{0} !== '-' ? $_SERVER["argv"][$i+1] : true ); if ($_SERVER["argv"][$i]{1} === '-') { // long argument $argv[substr($_SERVER["argv"][$i], 2)] = $value; } else { foreach (str_split($_SERVER["argv"][$i]) as $arg) { if (ereg('[a-zA-Z0-9]', $arg)) { $last_arg = $arg; $argv[$arg] = true; } } $argv[$last_arg] = $value; } } } define('SF_APP', (isset($argv['app'])?$argv['app']:'www')); define('SF_ENVIRONMENT', (isset($argv['env'])?$argv['env']:'prod')); define('SF_DEBUG', (isset($argv['debug'])?1:0)); require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');
Then, start your batch file with :
<?php require_once(realpath(dirname(__file__).'/..').DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'configBatch.php'); // Your script start here
Now, you can pass arguments to you script :
php -f /path/to/project/batch/my_batch.php -rf some_value --help --foo bar
Here is the generated $argv :
$argv = array ( 'r' => true, 'f' => 'some_value', 'help' => true, 'foo' => 'bar', );
You can also change the SF_ENVIRONMENT with the --env argument, the SF_APP with the --app argument and the debug flag with --debug argumnent
Enjoy
I was looking for this type of functionality:
'php batch/mybatch.php app=batch env=dev'
The following allows me to use the same batch scripts for different environments and applications. It also keeps batch scripts lean by only requiring one line.
batch/run_me.php:
require_once('lib/batch.php'); // // begin batch scripting here //
batch/lib/batch.php:
set_time_limit(0); $app = get_app($argv[1]); $env = get_env($argv[2]); $debug = get_debug($argv[3]); // // initialize symfony // define('SF_ROOT_DIR', realpath(dirname(__FILE__).'/../..')); define('SF_APP', $app); define('SF_ENVIRONMENT', $env); define('SF_DEBUG', $debug); require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php'); sfContext::getInstance(); // // initialize doctrine (just an example) // $connection = sfDoctrine::connection(); // // parse command line args // function get_app($argv) { preg_match('@app=(\w+)@', $argv, $match); return isset($match[1]) ? $match[1] : 'batch'; } function get_env($argv) { preg_match('@env=(\w+)@', $argv, $match); return isset($match[1]) ? $match[1] : 'dev'; } function get_debug($argv) { preg_match('@(true|false)@', $argv, $match); return isset($match[1]) ? $match[1] : true; }
Problem:
Mysql produce TINYINT fields because BOOLEAN is just a synonym for TINYINT. And if you have TINYINT fields with value 0 or 1 (like BOOLEAN fields) in your database and use command symfony propel-build-schema to generate schema, you will have one problem with admin generator, because it will produse TINYINT fields as text form fields with value - 0 or 1, not as checkboxes. Admin generator needs BOOLEAN fields in schema to produse checkboxes.
Solution:
Solution to automate transformation field type tinyint to boolean where field name prefix is "is_".
sfPakeTransformTinyint.php
<?php pake_desc( 'apply tinyint-boolean transformation to your data model' ); pake_task( 'transform-schema-tinyint', 'project_exists' ); function run_transform_schema_tinyint( $task, $args ) { // Check params // -- missing params ? if ( !count($args) ) { throw new Exception( 'You must provide a transformation to apply.' ); } // -- schema exists ? $schema_filename = sprintf( '%s/schema.xml', sfConfig::get('sf_config_dir') ); if ( !file_exists($schema_filename) ) { throw new Exception( "Missing schema.xml" ); } // Backup schema pake_copy( $schema_filename, $schema_filename . '.previous', array('override' => true) ); //do hard work - tinyint->boolean if ($args[0] == 'do') { $handle = fopen($schema_filename, "r"); $contents = ''; while (!feof($handle)) { $contents .= fread($handle, 8192); } fclose($handle); $contents = preg_replace('/(name="is_.*?type=")TINYINT"/i','$1BOOLEAN"',$contents); $handle = fopen($schema_filename, "w+"); fwrite($handle, $contents); fclose($handle); } //undo hard work - boolean->tinyint if ($args[0] == 'undo') { $handle = fopen($schema_filename, "r"); $contents = ''; while (!feof($handle)) { $contents .= fread($handle, 8192); } fclose($handle); $contents = preg_replace('/(name="is_.*?type=")BOOLEAN"/i','$1TINYINT"',$contents); $handle = fopen($schema_filename, "w+"); fwrite($handle, $contents); fclose($handle); } } ?>
copy this code and drop it as new file in SF_DATA_DIR/tasks/ use command: symfony transform-schema-tinyint do to change tinyint to boolean, where field name with "is_" prefix, than rebuild your model with propel-build-model, clear cache, and use admin generator with checkboxes.
If something going wrong, do this command to undo changes in your model:
symfony transform-schema-tinyint undo (to change boolean to tinyint, where field name with "is_" prefix)
or you may use schema.xml.previous <- this is your schema before transformation
Just add this to your ~/.bashrc
alias sfcleanup='symfony propel-build-model && symfony propel-build-sql && symfony propel-insert-sql && php batch/load_data.php && symfony cc'
Next time you want to do a cleanup just type sfcleanup at the project dir.
We wanted to use a 'propel-insert-sql' in our acceptance tests suite to clear DB before every test reducing interferences. We all learned here http://www.symfony-project.com/snippets/snippet/16 how to call a Pake task from our PHP code. To get a quiet 'propel-insert-sql' task letting 'test' task be verbose and reporting test results we must add a method to the pakeTask class in pakeTask.class.php file:
public function setVerbose() { $this->verbose = false; }
and edit sfPakePropel.php file to make 'propel-insert-sql' task quiet:
function run_propel_insert_sql($task, $args) { $task->setVerbose(); _call_phing($task, 'insert-sql'); }
This way we have a lot quiter acceptance test suite and a clean DB whenever we want.
I find boring to wait to run all the tests while I'm focusing on a single one or few ones. Thus I decided to hack pake simpletest task to accept test group selection.
1st step:
In your Pake tasks folder (eg.: /usr/share/pear/pake/tasks) edit the pakeSimpletestTask.class.php file
public static function call_simpletest($task, $type = 'text', $dirs = array(), $test_file = null)
and add this few lines replacing the single line 48
if (!$test_file) { $files = pakeFinder::type('file')->name('*Test.php')->in($test_dirs); } else { $files = pakeFinder::type('file')->name($test_file)->in($test_dirs); }
This way you are making your pake test task able to accept "hints" about which file to load in the test folder.
2nd step:
Open the sfPakeTest.php file in your symfony/tasks folder and do the following: Add
$test_file = null; if (count($args) > 1) { $test_file = $args[1]; }
at line 14, then change last line into
pakeSimpletestTask::call_simpletest($task, 'text', $dirs_to_test, $test_file);
That's all.
Now you can launch symfony test <app_name> <test_filename> and have this only executed with no loss of time. You can even use wildcards!!! For example: symfont test myportal *ValidatorTest.php It will execute all the tests named with that pattern in your test/myportal folder.
Super confortable testing at last! Test driven programming requires very flexible test groups approach and this could be a small contribution. Hope it helps. Bye!
It can be tricky sending email from a batch script (e.g. for cron use), here's how to do it. Start with your usual batch setup:
define('SF_ROOT_DIR', realpath(dirname(__FILE__).'/..')); define('SF_APP', 'app_name'); define('SF_ENVIRONMENT', 'environment'); define('SF_DEBUG', true); require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php'); sfContext::getInstance();
You can set attributes directly from the batch script if you wish:
sfContext::getInstance()->getRequest()->setAttribute('key', $value);
Then forward on to another module/action to handle processing and forwarding to mail as usual:
sfContext::getInstance()->getController()->forward('action', 'module');
You can mimick the call of the symfony command line via a PHP script called from the browser. For instance, for the command
$ symfony clear-cache
...create a webclearcache.php file in your myproject/web/ directory (where the index.php is) with the following content:
<?php // as we are in the web/ dir, we need to go up one level to get to the project root chdir(dirname(__FILE__).DIRECTORY_SEPARATOR.'..'); include_once('/lib/symfony/pake/bin/pake.php'); $pake = pakeApp::get_instance(); try { $ret = $pake->run('/data/symfony/bin/pakefile.php', 'clear-cache'); } catch (pakeException $e) { print "<strong>ERROR</strong>: ".$e->getMessage(); } ?>
You can now clear the cache by calling:
http://myapp.example.com/webclearcache.php
Note: Beware that by letting web access to administration tools, you can compromise the safety of your website.
If you have not a PEAR symfony installation, but rather a couple of symlinks in the lib/ and data/ directories of your project, and if you didn't define shortcuts for the symfony command, you'll have to call manually pake (the tool used for the command line interface) and pass it the right pakefile (the configuration file containing the symfony commands).
This can be done that way:
$ cd /home/production/myproject
$ php /path/to/pake.php -f data/symfony/bin/pakefile.php propel-build-model
In systems where both PHP4 and PHP5 are installed, you probably need to use a PHP5 specific command:
$ php5 /usr/local/php5.1.2/lib/php/pake.php -f data/symfony/bin/pakefile.php propel-build-model