Propel Prune Classes Task
Over the years of working with the Symfony PHP framework I have had moments of conflict about whether I made the right choice to use the Propel ORM. Both Propel and Doctrine have amazing features but ultimately I like the Propel style. Development on the project waxes and wanes but as of late it has been moving at a rapid pace. One feature I have always liked about Doctrine that Propel does not have is pruning classes. For example if I have a schema file and decide to change the name of a table or delete a table altogether, all of my model, form and filter classes from the previous table remain. Since there are many folders in which these auto-generated classes reside it’s a huge pain to go through them all and remove them. Today I wrote a quick little Symfony task to take care of this for me and I thought I’d share it with the rest of the Symfony+Propel world. Since I needed this quickly it’s a little dirty and only works with a filename named “schema.xml” so if you need it to work with multiple XML schemas you will have to tweak the code a little. I didn’t bother with YML schema files because I don’t use them, enjoy!
The code is listed below or you may clone a copy from github.
Save the code below in a file lib/task/PropelPruneClassesTask.class.php
Usage: ./symfony propel:prune-classes –exclude=”MyCustomFormClass.class.php MyCustomFileInModel.php”
The –exclude flag is optional. If you have any custom classes of your own that reside in your lib/model, lib/filter or lib/form folders you include a space-delimited list of them in the exclude option.
< ?php
class PropelPruneClassesTask extends sfBaseTask
{
private $prefixes;
private $postfixes;
private $tableNames;
private $exclude;
private $toDelete;
public function configure() {
$this->namespace = 'propel';
$this->name = 'prune-classes';
$this->briefDescription = 'Removes unused model, filter and form classes';
$this->addOptions(array(
new sfCommandOption('application', null, sfCommandOption::PARAMETER_REQUIRED, 'The application name', 'frontend'),
new sfCommandOption('env', null, sfCommandOption::PARAMETER_REQUIRED, 'The environment', 'prod'),
new sfCommandOption('exclude', null, sfCommandOption::PARAMETER_OPTIONAL, 'A space delimited list of files to exclude from removal', "BaseForm.class.php BaseFormPropel.class.php BaseFormFilterPropel.class.php"),
));
}
public function execute($arguments = array(), $options = array()) {
$this->toDelete = array();
$deleteCount = 0;
$this->exclude = array("BaseForm.class.php", "BaseFormPropel.class.php", "BaseFormFilterPropel.class.php");
if (!is_null($options['exclude']))
$this->exclude = array_merge($this->exclude, explode(' ', $options['exclude']));
$schemaXmlFile = sfConfig::get('sf_config_dir')."/schema.xml";
$fp = fopen($schemaXmlFile, "r");
$contents = fread($fp, filesize($schemaXmlFile));
$xml = new SimpleXMLElement($contents);
foreach ($xml->table as $table) {
$rawTableName = $table['name'];
$this->tableNames[] = str_replace(' ', '', ucwords(str_replace('_', ' ', $rawTableName)));
}
$libDir = sfConfig::get('sf_lib_dir');
$modelDir = $libDir.'/model';
$baseModelDir = $modelDir."/om";
$mapDir = $modelDir."/map";
$formDir = $libDir.'/form';
$baseFormDir = $formDir.'/base';
$filterDir = $libDir.'/filter';
$baseFilterDir = $filterDir.'/base';
$this->prefixes = array();
$this->postfixes = array('Peer', 'Query');
$this->buildFileRemovalList($modelDir);
$this->prefixes = array('Base');
$this->postfixes = array('Peer', 'Query');
$this->buildFileRemovalList($baseModelDir);
$this->prefixes = array();
$this->postfixes = array('TableMap');
$this->buildFileRemovalList($mapDir);
$this->postfixes = array('Form.class');
$this->buildFileRemovalList($formDir);
$this->prefixes = array('Base');
$this->postfixes = array('Form.class');
$this->buildFileRemovalList($baseFormDir);
$this->prefixes = array();
$this->postfixes = array('FormFilter.class');
$this->buildFileRemovalList($filterDir);
$this->prefixes = array('Base');
$this->postfixes = array('FormFilter.class');
$this->buildFileRemovalList($baseFilterDir);
if (count($this->toDelete) > 0) {
$doDelete = $this->askConfirmation("The following files are scheduled for deletion: \n\n".implode("\n", $this->toDelete)."\n\n Perform deletion? (y/n)");
if ($doDelete) {
foreach ($this->toDelete as $d) {
if (unlink($d)) {
$this->logSection('file-', $d);
$deleteCount++;
}
else
$deleteError[] = $d;
}
if (count($deleteError) > 0)
$this->logBlock("The following files could not be removed: \n\n".implode("\n", $deleteError), 'ERROR_LARGE');
}
}
if ($deleteCount == 1)
$this->logBlock($deleteCount." file pruned", 'INFO_LARGE');
else
$this->logBlock($deleteCount." files pruned", 'INFO_LARGE');
}
private function buildFileRemovalList($dirPath) {
$deleteError = array();
$dirHandle = opendir($dirPath);
$masterFileList = $this->tableNames;
foreach ($this->prefixes as $prefix) {
foreach ($masterFileList as $tableName)
$masterFileList[] = $prefix.$tableName;
}
foreach ($this->postfixes as $postfix) {
foreach ($masterFileList as $tableName)
$masterFileList[] = $tableName.$postfix;
}
$appendPhpExt = function($filename) {
return $filename.".php";
};
$masterFileList = array_map($appendPhpExt, $masterFileList);
while (false !== ($file = readdir($dirHandle))) {
if ($file[0] != '.' && !is_dir($dirPath."/".$file) && !in_array($file, $this->exclude)) {
if (!in_array($file, $masterFileList))
$this->toDelete[] = $dirPath."/".$file;
}
}
closedir($dirHandle);
}
}
?>
Leave a Reply
Want to join the discussion?Feel free to contribute!