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';
      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->prefixes = array('Base');
		$this->postfixes = array('Peer', 'Query');
		$this->prefixes = array();
		$this->postfixes = array('TableMap');
		$this->postfixes = array('Form.class');
		$this->prefixes = array('Base');
		$this->postfixes = array('Form.class');
		$this->prefixes = array();
		$this->postfixes = array('FormFilter.class');
		$this->prefixes = array('Base');
		$this->postfixes = array('FormFilter.class');
		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);
						$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');
			$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;

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *