phpSoCo, for counting php lines of code

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

User avatar
Mordred
DevNet Resident
Posts: 1579
Joined: Sun Sep 03, 2006 5:19 am
Location: Sofia, Bulgaria

Post by Mordred »

scottayy wrote:
Mordred wrote:Parsing PHP code with regexps is [s]hopelessly[/s] seriously flawed.
Use proper parrsing via the tokenizer functions.
That's a good idea. So far I'm having success with regex's, though.

Code: Select all

echo "http://google.com";
I bet you'd count it as

Code: Select all

echo "http:
scottayy wrote:
Mordred wrote:count_hard (and therefore count_all) are useless stats, might as well give character count or i dunno, measure whitespace to text ratio :)
You've got to be kidding? I enjoy count_hard stats because it's the most compact. Gives me a MINIMUM boundary that I can definately say my code is at least X lines by X characters long.

And I don't think the count_all would be useless, in fact, it'd be the most useful.
and btw your db code sucks :)
just kidding (literally, got my kid in the other hand, so excuse tha lame typing :P )

(but seriously, why the thin wrapper, I doubt you ever used even half the methods)
I've never even used it. It was a boredom thing, much like this. :)
Count_hard seems useless, because nobody else in the world is counting their lines like that.

Q: How many lines is your lib?
S: 30.45 lines of 80 chars each, ignoring the whitespace
Q: Wtf? I ask for line count, not char count, you're writing PHP, not a novel.

By extension, an average stat which includes an useless stat becomes useless as well (even if it weren't the first time).
Count lines with/without:
1. Comments
2. Empty lines
3. Lines with a single opening or closing bracket

This will make sense to a much wider audience.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

So, we'll take into account that my method of counting lines sucks. I'll add the ways you guys suggested as options in my next version update. So, I'm still using the same method of counting lines in this update, but look past that till I update it. I've split the class up into 4 classes. A base class, a class for single files, a class for a directory, and a class for a directory tree. This is about as far into OO solutions as I've gotten, so some of it might suck.

Todo: Better regex or use tokenizer functions. Right now <?php <? // and /* */ will get truncated if inside of a string (shouldn't do that).
Todo: Multiple counting methods, let the user decide which one to use.

phpsloc.class.php

Code: Select all

<?php

/**
 * phpsloc will allow you to evaluate any php script to determine the number of 
 * lines and characters in that code.  It will strip all comments and white 
 * space, and get the remaining code into a single sting that is then chunked 
 * into a line-length that you specify.  You can also specify whether to include 
 * non-php code (html, css, js, etc) that's in the php file, or php only (excludes 
 * everything else).  This defaults to php only.  You can also optionally strip
 * all single spaces ($var = $value verses $var=$value) and optionally strip php 
 * opening and closing tags.
 *
 * @Version 1.0.0
 *     - Initial Release
 *
 * @Version 1.0.1
 *     - Added # style comments to be removed
 *     - If _PHPOnly is set to false, HTML style comments will be removed
 *     - replaced php opening/closing tags with str_ireplace() to avoid potential 
 *       casing issues.
 *     - Added a _returnHTML class member to return HTML instead of passing it as
 *       a parameter to the getStats() method.
 *     - Added a setter method setReturnHTML() to set the desired output style
 *
 * @Version 1.1.1
 *     - Broke the main class up into 4 classes, phpsloc() base class, phpsloc_file(), 
 *       phpsloc_directory() and phpsloc_directoryTree()
 *     - Allows for single directory, and a directory tree parsing for files
 *     - Base class phpsloc() method getStats() has different parameters and instantiates
 *       a new object based on the $input parameter.
 *
 *
 * @todo
 *     - Use a better regex to not strip comments or opening/closing php tags inside of strings
 *       or use tokenizer functions
 *     - Use a better method of counting lines, or let the user decide from a variety of methods
 *
 * @author Scott Martin <smp_info[at]yahoo[dot]com>
 * @date November 27th, 2007
 */
class phpsloc
{
	/**
	 * Container for each files code source
	 */
	protected $_code;
	
	/**
	 * Evaluate PHP only code
	 */
	protected $_PHPOnly = true;
	
	/**
	 * Allow PHP tags
	 */
	protected $_allowPHPTags = false;
	
	/**
	 * The number of characters at which this class determines a full line, when 
	 * reached, the next line will begin at this + 1 characters
	 */
	protected $_lineLength = 80;
	
	/**
	 * Allow single spaces in the code?  $foo = 'bar'; versus $foo='bar';
	 */
	protected $_allowSingleSpace = false;
	
	/**
	 * When the last line doesn't reach $_lineLength characters, how many decimal 
	 * places should we round to?
	 */
	protected $_lineCountPrecision = 2;
	
	/**
	 * Holds the line count of the file after this class has prepared it
	 */
	protected $_lineCount;
	
	/**
	 * Holds the character count of the file's code
	 */
	protected $_characterCount;
	
	/**
	 * Whether to return HTML or not.  The default return is an array
	 * Set to true to return HTML.
	 */
	protected $_returnHTML = false;
	
	/**
	 * If _returnHTML is set to true, this boolean determines whether to send a
	 * full HTML page with headers or not.
	 */
	protected $_returnHTMLFull = true;
	
	/**
	 * Set the desired line length.  Defaults to 80 (standards-esque)
	 * @param integer $lineLength - the maximum length of one line of code
	 * @access public
	 */
	public function setLineLength($lineLength)
	{
		$this->_lineLength = (int) $lineLength;
	}
	
	/**
	 * Set to true to only include php code, false to include all code in file 
	 * contents (html, css, js, etc)
	 * @param boolean $bool - true to only parse php code, false to parse all code
	 * @access public
	 */
	public function setPHPOnly($bool)
	{
		$this->_PHPOnly = (bool) $bool;
	}
	
	/**
	 * Set to true if you want <? <?php and ?> to be included in the code, 
	 * otherwise set to false
	 * @param boolean $bool
	 * @access public
	 */
	public function setAllowPHPTags($bool)
	{
		$this->_allowPHPTags = (bool) $bool;
	}
	
	/**
	 * Sets whether or not to allow single spaces in the code
	 * @param boolean $bool
	 * @access public
	 */
	public function allowSingleSpace($bool)
	{
		$this->_allowSingleSpace = (bool) $bool;
	}
	
	/**
	 * Set the number of decimal points to round the lines of code to
	 * @param integer $int
	 * @access public
	 */
	public function setLineCountPrecision($int)
	{
		$this->_lineCountPrecision = (int) $int;
	}
	
	/**
	 * Set to return HTML instead of the default array returned
	 * @param boolean $bool - true to return html, false to return array
	 * @access public
	 */
	public function setReturnHTML($bool)
	{
		$this->_returnHTML = (bool) $bool;
	}
	
	/**
	 * Set to false to return only the body of the html generated, or set to true 
	 * to return a full HTML page with headers.
	 * @param boolean $bool
	 * @access public
	 */
	public function setReturnHTMLFull($bool)
	{
		$this->_returnHTMLFull = (bool) $bool;
	}
	
	/**
	 * Instantiates a new object, generating data, then returns it
	 * @param string $input - the file or directory to be evaluated
	 * @param boolean $recurse - if $input is a directory, whether or not to 
	 * recurse the directory tree.
	 * @return mixed - array when _returnHTML is false, string when true
	 * @access public
	 */
	public function getStats($input, $recurse=false)
	{
		if (is_file($input))
		{
			$ret = new phpsloc_file($input);
		} elseif (is_dir($input) && !$recurse)
		{
			$ret = new phpsloc_directory($input);
		} elseif (is_dir($input) && $recurse)
		{
			$ret = new phpsloc_directoryTree($input);
		} else
		{
			trigger_error('PHPsloc: Could not evaluate input file or directory', E_USER_ERROR);
		}
		
		//set object properties
		$ret->_returnHTML = $this->_returnHTML;
		$ret->_returnHTMLFull = $this->_returnHTMLFull;
		$ret->_PHPOnly = $this->_PHPOnly;
		$ret->_allowPHPTags = $this->_allowPHPTags;
		$ret->_allowSingleSpace = $this->_allowSingleSpace;
		$ret->_lineLength = $this->_lineLength;
		$ret->_lineCountPrecision = $this->_lineCountPrecision;
		
		return $ret->_getStat();
	}
	
	/**
	 * Gathers php blocks from code
	 * @access private
	 */
	protected function _getPHPCode()
	{
		if ($this->_PHPOnly)
		{
			//multiple php blocks, or single php block with closing tag
			if (preg_match_all("/<\?(php)?.+?\?>/sm", $this->_code, $matches))
			{
				$this->_code = implode("\n", $matches[0]);
			} 
		}
	}
	
	/**
	 * Strips php opening and closing tags from the string if configured to
	 * @access private
	 * @todo - probably need a regex to make sure the tags aren't in strings
	 */
	protected function _handleTags()
	{
		if (!$this->_allowPHPTags)
		{
			$this->_code = str_ireplace(array('<?php', '<?', '?>'), '', $this->_code);
		}
	}
	
	/**
	 * Strips the files contents of white space characters
	 * @access private
	 */
	protected function _stripWhiteSpace()
	{
		//replace whitespace characters with a single space
		$this->_code = preg_replace("#\s{1,}#m", ' ', $this->_code);
		
		if ($this->_allowSingleSpace === false)
		{
			$this->_code = str_replace(' ', '', $this->_code);
		}
	}
	
	/**
	 * Strips the files contents of comments
	 * @todo make sure the comment isn't inside of a string
	 * @access private
	 */
	protected function _stripComments()
	{
		//php comments (also css comments and javascript comments)
		$this->_code = preg_replace("#/\*.+?\*/#sm", '', $this->_code);
		$this->_code = preg_replace("#//.+#", '', $this->_code);
		$this->_code = preg_replace("#\#.+#", '', $this->_code);
		
		//strip HTML comments?
		if (!$this->_PHPOnly)
		{
			$this->_code = preg_replace("#<!--.+?-->#sm", '', $this->_code);
		}
	}
	
	/**
	 * Counts the characters in the phpsloc formatted code
	 * @access private
	 */
	protected function _getCharacterCount()
	{
		$this->_characterCount = strlen($this->_code);
	}
	
	/**
	 * Splits the phpsloc formatted code into lines of specified length
	 * @access private
	 */
	protected function _formatLineLength()
	{
		$this->_code = chunk_split($this->_code, $this->_lineLength);
	}
	
	/**
	 * Counts the number of lines in the phpsloc formatted code, taking into 
	 * consideration the last line.  If it is not a "full" line, it will be 
	 * represented as a float value
	 * @access private
	 */
	protected function _getLineCount()
	{
		//count lines
		$lines = array_filter(explode("\r\n", $this->_code));
		$lineCount = count($lines);
		
		//count characters in the last line
		$lastLine = strlen(array_pop($lines));
		$float = $lastLine != $this->_lineLength ? $lastLine/$this->_lineLength : 1;
		
		//add
		$this->_lineCount = round($lineCount - (1 - $float), $this->_lineCountPrecision);
	}
	
	/**
	 * Generates HTML for output
	 * @param array $arr - array of generated stats
	 * @return string
	 * @access protected
	 */
	protected function _generateHTML($arr)
	{
		$htmlOutput = '';
		foreach ($arr AS $k => $v)
		{
			//$htmlOutput .= '';
			if (is_array($v))
			{
				$htmlOutput .= '<p><strong>' . str_replace('_', ' ', $k) . '</strong></p>';
				$htmlOutput .=  $this->_generateHTML($v, false);
			} else
			{
				$htmlOutput .=  !is_numeric($k) ? str_replace('_', ' ', $k) . ': <strong>' . $v . '</strong><br>' : '<strong>' . $v . '</strong><br>';
			}
		}
		return $htmlOutput;
	}
	
	/**
	 * Generates a standards compliant HTML header for HTML output.
	 * @param string $type - the type of evaluation being done
	 * @access protected
	 */
	protected function _HTMLHeader($type)
	{
		return '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
		   "http://www.w3.org/TR/html4/loose.dtd">
		<html>
		<head>
		<title>phpsloc ' . $type . ' Evaluation</title>
		<meta http-equiv="Content-type" content="text/html;UTF-8">
		<style type="text/css">
		body
		{
			background-color: #fff;
			color: #000;
			font-family: "courier new", courier, verdana, arial;
			font-size: 13px;
		}
		</style>
		</head>
		<body>';
	}
	
	/**
	 * Closes standards compliant full HTML output
	 * @ access protected
	 */
	protected function _HTMLFooter()
	{
		return '</body>
		</html>';
	}
	
}





/**
 * This is the class that deals with a single file.  It will evaluate only a single file.  
 * It can be used on single file input, directory input, or directory tree input.
 */
class phpsloc_file extends phpsloc
{
	/**
	 * The name of the file to be evaluated
	 */
	private $_file;

	/**
	 * Constructor to set this class's $_file property, and parent class $_code property
	 * @param $file - string of file name
	 * @access protected
	 */
	protected function __construct($file)
	{
		$this->_file = $file;
		$this->_code = file_get_contents($file);
	}
	
	/**
	 * Runs through class methods generating stats and returns them.  Either as HTML 
	 * or an array.
	 * @access protected
	 */
	protected function _getStat()
	{
		$this->_getPHPCode();
		$this->_handleTags();
		$this->_stripComments();
		$this->_stripWhiteSpace();
		$this->_getCharacterCount();
		$this->_formatLineLength();
		$this->_getLineCount();
		
		//write the return array
		$ret = array(
			'phpsloc_Configuration' => array(
				'PHP_Code_Only' => $this->_PHPOnly ? 'Yes' : 'No',
				'Line_Length_Used' => $this->_lineLength,
				'Line_Count_Float_Precision' => $this->_lineCountPrecision,
				'Spaces_Included' => $this->_allowSingleSpace ? 'Yes' : 'No',
				'PHP_Tags_Included' => $this->_allowPHPTags ? 'Yes' : 'No',
				'Mode_Used' => 'Single File'
			),
			
			'Code_Stats' => array(
				'File_Evaluated' => $this->_file,
				'Lines_Of_Code' => $this->_lineCount,
				'Characters_In_Code' => $this->_characterCount
			)
		);
		
		//if HTML is the preferred method of return, return it
		if ($this->_returnHTML)
		{
			if ($this->_returnHTMLFull)
			{
				return print $this->_HTMLHeader('File') . $this->_generateHTML($ret) . $this->_HTMLFooter();
			} else
			{
				return print $this->_generateHTML($ret);
			}
		}
		
		//just return the array
		return $ret;
	}
}





/**
 * This class evaluated a single directory, and is also used in recursive directory trees.  
 * Each file found in the directory is passed to phpsloc_file() for evaluating each 
 * individual file.
 */
class phpsloc_directory extends phpsloc_file
{
	/**
	 * Holds the directory to be evaluated
	 */
	private $_directory;
	
	/**
	 * Holds the array of php files found in the directory
	 */
	private $_foundFiles;
	
	/**
	 * Constructor method sets the directory to be used
	 * @param string $directory
	 * @access protected
	 */
	protected function __construct($directory)
	{
		if (substr($directory, -1) == DIRECTORY_SEPARATOR)
		{
			$this->_directory = substr($directory, 0, strlen($directory-1));
		} else
		{
			$this->_directory = $directory;
		}
	}
	
	/**
	 * Runs through methods in this class, ultimately returning stats.
	 * @access protected
	 * @return mixed - array or string (depending on settings)
	 */
	protected function _getStat()
	{
		//find the files
		$this->_findFiles();
		
		//if HTML is the preferred return method, return it
		if ($this->_returnHTML)
		{
			if ($this->_returnHTMLFull)
			{
				//return html with headers
				return print $this->_HTMLHeader('Directory') . $this->_generateHTML(
						$this->_compoundSingleStats(
							$this->_evaluateSingleFiles()
						)
					) . $this->_HTMLFooter();
			} else
			{
				//return html without headers
				return print $this->_generateHTML(
						$this->_compoundSingleStats(
							$this->_evaluateSingleFiles()
						)
					);
			}
		}
		
		//return the array
		return $this->_compoundSingleStats($this->_evaluateSingleFiles());
	}
	
	/**
	 * Grabs all of the php files found in this directory and stores the array in 
	 * class member.
	 * @access private
	 */
	private function _findFiles()
	{
		$this->_foundFiles = glob($this->_directory . DIRECTORY_SEPARATOR . '*.php');
	}
	
	/**
	 * Loops through each found file and creates a new phpsloc_file() object.  
	 * Stores the returned array of stats in return value, then returns it.
	 * @return array
	 * @access private
	 */
	private function _evaluateSingleFiles()
	{
		//if we have files
		if (!empty($this->_foundFiles))
		{
			//loop through, gather single stats
			$ret = array();
			foreach ($this->_foundFiles AS $file)
			{
				$single = new phpsloc_file($file);
				$single->_returnHTML = false;
				$single->_PHPOnly = $this->_PHPOnly;
				$single->_allowPHPTags = $this->_allowPHPTags;
				$single->_allowSingleSpace = $this->_allowSingleSpace;
				$single->_lineLength = $this->_lineLength;
				$single->_lineCountPrecision = $this->_lineCountPrecision;
				$ret[] = $single->_getStat();
			}
		} else
		{
			//no files, return empty array
			$ret = array();
		}
		
		//return array of found file stats
		return $ret;
	}
	
	/**
	 * Compounds each files single stats into an array of stats for the 
	 * directory.  This is really ugly at the moment.
	 * @param array $stats
	 * @return array
	 */
	private function _compoundSingleStats($stats)
	{
		//if we have stats
		if (!empty($stats))
		{
			//set up return array
			$ret['phpsloc_Configuration'] = $stats[0]['phpsloc_Configuration'];
			$ret['phpsloc_Configuration']['Mode_Used'] = 'Directory';
			$ret['Code_Stats']['Directory_Evaluated'] = '';
			$ret['Code_Stats']['Files_Evaluated'] = array();
			$ret['Code_Stats']['Summary'] = array();
			$ret['Code_Stats']['Summary']['Lines_Of_Code'] = 0;
			$ret['Code_Stats']['Summary']['Characters_In_Code'] = 0;
			$ret['Code_Stats']['Average'] = array();
			$ret['Code_Stats']['Average']['Lines_Of_Code'] = 0;
			$ret['Code_Stats']['Average']['Characters_In_Code'] = 0;
			$ret['Code_Stats']['Single_File_Stats'] = array();
			
			//loop through each, setting and adding stats
			$i = 0;
			foreach ($stats AS $stat)
			{
				if ($i == 0)
				{
					$ret['Code_Stats']['Directory_Evaluated'] = implode(DIRECTORY_SEPARATOR, 
						array_diff(explode(DIRECTORY_SEPARATOR, $stat['Code_Stats']['File_Evaluated']), 
						array(array_pop(explode(DIRECTORY_SEPARATOR, $stat['Code_Stats']['File_Evaluated'])))));
				}
				
				$ret['Code_Stats']['Files_Evaluated'][] = $stat['Code_Stats']['File_Evaluated'];
				$ret['Code_Stats']['Summary']['Lines_Of_Code'] += $stat['Code_Stats']['Lines_Of_Code'];
				$ret['Code_Stats']['Summary']['Characters_In_Code'] += $stat['Code_Stats']['Characters_In_Code'];
				$ret['Code_Stats']['Single_File_Stats'][$stat['Code_Stats']['File_Evaluated']] = array(
					'Lines_Of_Code' => $stat['Code_Stats']['Lines_Of_Code'],
					'Characters_In_Code' => $stat['Code_Stats']['Characters_In_Code']
				);
				
				$i++;
			}
			
			//here we will get directory averages
			$ret['Code_Stats']['Average']['Lines_Of_Code'] =
				round($ret['Code_Stats']['Summary']['Lines_Of_Code'] / count($stats), $this->_lineCountPrecision);
			
			$ret['Code_Stats']['Average']['Characters_In_Code']
				= round($ret['Code_Stats']['Summary']['Characters_In_Code'] / count($stats), $this->_lineCountPrecision);
		} else
		{
			//we have nothing
			return array();
		}
		
		//return
		return $ret;
	}
}





class phpsloc_directoryTree extends phpsloc_directory
{
	/**
	 * Holds the root directory of the directory tree
	 */
	private $_directory;
	
	/**
	 * Sets the root directory of the directory tree
	 * @param string $directory
	 * @access protected
	 */
	protected function __construct($directory)
	{
		if (substr($directory, -1) == DIRECTORY_SEPARATOR)
		{
			$this->_directory = substr($directory, 0, strlen($directory-1));
		} else
		{
			$this->_directory = $directory;
		}
	}
	
	/**
	 * Generates stats and returns them
	 * @access protected
	 */
	protected function _getStat()
	{
		//find the directories
		$directories = $this->_findDirectories($this->_directory);
		
		//unshift root directory onto the beginning
		array_unshift($directories, $this->_directory);
		
		//get the stats
		$ret = $this->_compoundDirectories($this->_evaluateDirectories($directories));
		
		//return
		if ($this->_returnHTML)
		{
			if ($this->_returnHTMLFull)
			{
				return print $this->_HTMLHeader('Directory Tree') . $this->_generateHTML($ret) . $this->_HTMLFooter();
			}
			
			return print $this->_generateHTML($ret);
		}
		
		return $ret;
	}
	
	/**
	 * Compounds directory stats into a single array
	 * @param array $stats
	 * @access private
	 */
	private function _compoundDirectories($stats)
	{
		if (!empty($stats))
		{
			//set up return array
			$ret['phpsloc_Configuration'] = $stats[0]['phpsloc_Configuration'];
			$ret['phpsloc_Configuration']['Mode_Used'] = 'Directory Tree';
			$ret['Code_Stats']['Directory_Tree_Evaluated'] = $this->_directory;
			$ret['Code_Stats']['Directories_Evaluated'] = array();
			$ret['Code_Stats']['Files_Evaluated'] = array();
			$ret['Code_Stats']['Summary'] = array();
			$ret['Code_Stats']['Summary']['Lines_Of_Code'] = 0;
			$ret['Code_Stats']['Summary']['Characters_In_Code'] = 0;
			$ret['Code_Stats']['Average_Per_Directory'] = array();
			$ret['Code_Stats']['Average_Per_Directory']['Lines_Of_Code'] = 0;
			$ret['Code_Stats']['Average_Per_Directory']['Characters_In_Code'] = 0;
			$ret['Code_Stats']['Average_Per_File'] = array();
			$ret['Code_Stats']['Average_Per_File']['Lines_Of_Code'] = 0;
			$ret['Code_Stats']['Average_Per_File']['Characters_In_Code'] = 0;
			$ret['Code_Stats']['Single_File_Stats'] = array();
		
			foreach ($stats AS $stat)
			{
				$ret['Code_Stats']['Directories_Evaluated'][] = $stat['Code_Stats']['Directory_Evaluated'];
				$ret['Code_Stats']['Files_Evaluated'] = array_merge($ret['Code_Stats']['Files_Evaluated'], $stat['Code_Stats']['Files_Evaluated']);
				$ret['Code_Stats']['Summary']['Lines_Of_Code'] += $stat['Code_Stats']['Summary']['Lines_Of_Code'];
				$ret['Code_Stats']['Summary']['Characters_In_Code'] += $stat['Code_Stats']['Summary']['Characters_In_Code'];
				$ret['Code_Stats']['Single_File_Stats'] = array_merge($ret['Code_Stats']['Single_File_Stats'], $stat['Code_Stats']['Single_File_Stats']);
			}
			
			//here we will get directory averages
			$ret['Code_Stats']['Average_Per_Directory']['Lines_Of_Code'] =
				round($ret['Code_Stats']['Summary']['Lines_Of_Code'] / count($stats), $this->_lineCountPrecision);
			$ret['Code_Stats']['Average_Per_Directory']['Characters_In_Code']
				= round($ret['Code_Stats']['Summary']['Characters_In_Code'] / count($stats), $this->_lineCountPrecision);
				
			//here we will get single file averages
			$ret['Code_Stats']['Average_Per_File']['Lines_Of_Code'] =
				round($ret['Code_Stats']['Summary']['Lines_Of_Code'] / count($ret['Code_Stats']['Files_Evaluated']), $this->_lineCountPrecision);
			$ret['Code_Stats']['Average_Per_File']['Characters_In_Code']
				= round($ret['Code_Stats']['Summary']['Characters_In_Code'] / count($ret['Code_Stats']['Files_Evaluated']), $this->_lineCountPrecision);
		} else
		{
			//we have nothing
			return array();
		}
			
		return $ret;
	}
	
	/**
	 * Evaluate each single directory
	 * @param array $directories
	 */
	private function _evaluateDirectories($directories)
	{	
		//echo '<pre>';print_r($directories);echo'</pre>';
		$ret = array();
		foreach ($directories AS $directory)
		{
			//instantiate new directory object
			$dirObj = new phpsloc_directory($directory);
			
			//set object properties
			$dirObj->_returnHTML = false;
			$dirObj->_PHPOnly = $this->_PHPOnly;
			$dirObj->_allowPHPTags = $this->_allowPHPTags;
			$dirObj->_allowSingleSpace = $this->_allowSingleSpace;
			$dirObj->_lineLength = $this->_lineLength;
			$dirObj->_lineCountPrecision = $this->_lineCountPrecision;
			
			//add to ret array if not empty
			$stat = $dirObj->_getStat();
			if (!empty($stat))
			{
				$ret[] = $stat;
			}
		}
		
		return $ret;
	}
	
	/**
	 * Recursively find directories
	 * @param $start
	 * @access private
	 */
	private function _findDirectories($start)
	{
		$ret = array();
		$handle = opendir($start);
		while (($file = readdir($handle)) !== false)
		{
			$file = $start . DIRECTORY_SEPARATOR . $file;
			if (
				($file != $start . DIRECTORY_SEPARATOR . '.') && 
				($file != $start . DIRECTORY_SEPARATOR . '..')
			)
			{
				if (is_dir($file))
				{
					array_push($ret, $file);
					$ret = array_merge($ret, $this->_findDirectories($file));
				}
			}
		}
		
		return $ret;
	}
	
	
}
Single File

Code: Select all

//single file
$phpsloc = new phpsloc();
echo '<pre>';
print_r($phpsloc->getStats('c:\users\scott\desktop\db.mysql.php'));
echo '</pre>';

Code: Select all

Array
(
    [phpsloc_Configuration] => Array
        (
            [PHP_Code_Only] => Yes
            [Line_Length_Used] => 80
            [Line_Count_Float_Precision] => 2
            [Spaces_Included] => No
            [PHP_Tags_Included] => No
            [Mode_Used] => Single File
        )

    [Code_Stats] => Array
        (
            [File_Evaluated] => c:\users\scott\desktop\db.mysql.php
            [Lines_Of_Code] => 40.83
            [Characters_In_Code] => 3266
        )

)
Single Directory

Code: Select all

//single directory
$phpsloc = new phpsloc();
echo '<pre>';
print_r($phpsloc->getStats('C:\apache2\apache2\htdocs\crap'));
echo '</pre>';

Code: Select all

Array
(
    [phpsloc_Configuration] => Array
        (
            [PHP_Code_Only] => Yes
            [Line_Length_Used] => 80
            [Line_Count_Float_Precision] => 2
            [Spaces_Included] => No
            [PHP_Tags_Included] => No
            [Mode_Used] => Directory
        )

    [Code_Stats] => Array
        (
            [Directory_Evaluated] => C:\apache2\apache2\htdocs\crap
            [Files_Evaluated] => Array
                (
                    [0] => C:\apache2\apache2\htdocs\crap\bug.php

                    THROUGH (saves some space in this topic)

                    [7] => C:\apache2\apache2\htdocs\crap\test.php
                )

            [Summary] => Array
                (
                    [Lines_Of_Code] => 136.94
                    [Characters_In_Code] => 10954
                )

            [Average] => Array
                (
                    [Lines_Of_Code] => 17.12
                    [Characters_In_Code] => 1369.25
                )

            [Single_File_Stats] => Array
                (
                    [C:\apache2\apache2\htdocs\crap\bug.php] => Array
                        (
                            [Lines_Of_Code] => 0.11
                            [Characters_In_Code] => 9
                        )

                    THROUGH (saves some space in this topic)

                    [C:\apache2\apache2\htdocs\crap\test.php] => Array
                        (
                            [Lines_Of_Code] => 27.7
                            [Characters_In_Code] => 2216
                        )

                )

        )

)
Directory Tree

Code: Select all

//directory tree
$phpsloc = new phpsloc();
echo '<pre>';
print_r($phpsloc->getStats('C:\apache2\apache2\htdocs\smp', true));
echo '</pre>';

Code: Select all

Array
(
    [phpsloc_Configuration] => Array
        (
            [PHP_Code_Only] => Yes
            [Line_Length_Used] => 80
            [Line_Count_Float_Precision] => 2
            [Spaces_Included] => No
            [PHP_Tags_Included] => No
            [Mode_Used] => Directory Tree
        )

    [Code_Stats] => Array
        (
            [Directory_Tree_Evaluated] => C:\apache2\apache2\htdocs\smp
            [Directories_Evaluated] => Array
                (
                    [0] => C:\apache2\apache2\htdocs\smp
                    [1] => C:\apache2\apache2\htdocs\smp\admin
                    [2] => C:\apache2\apache2\htdocs\smp\class
                    [3] => C:\apache2\apache2\htdocs\smp\class\language
                    [4] => C:\apache2\apache2\htdocs\smp\contacts
                    [5] => C:\apache2\apache2\htdocs\smp\contacts\includes
                    [6] => C:\apache2\apache2\htdocs\smp\includes
                    [7] => C:\apache2\apache2\htdocs\smp\language
                )

            [Files_Evaluated] => Array
                (
                    [0] => C:\apache2\apache2\htdocs\smp\activate.php

                    THROUGH (saves space in this topic)

                    [252] => C:\apache2\apache2\htdocs\smp\language\phpmailer.lang-tr.php
                )

            [Summary] => Array
                (
                    [Lines_Of_Code] => 14704.18
                    [Characters_In_Code] => 1176317
                )

            [Average_Per_Directory] => Array
                (
                    [Lines_Of_Code] => 1838.02
                    [Characters_In_Code] => 147039.63
                )

            [Average_Per_File] => Array
                (
                    [Lines_Of_Code] => 58.12
                    [Characters_In_Code] => 4649.47
                )

            [Single_File_Stats] => Array
                (
                    [C:\apache2\apache2\htdocs\smp\activate.php] => Array
                        (
                            [Lines_Of_Code] => 40.91
                            [Characters_In_Code] => 3273
                        )

                     THROUGH (saves space in this topic)

                    [C:\apache2\apache2\htdocs\smp\language\phpmailer.lang-tr.php] => Array
                        (
                            [Lines_Of_Code] => 10.13
                            [Characters_In_Code] => 810
                        )

                )

        )

)
Instead of an array being returned, you can also return a full valid HTML page, or just the stats chunk of HTML.

Thoughts?
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
Mordred
DevNet Resident
Posts: 1579
Joined: Sun Sep 03, 2006 5:19 am
Location: Sofia, Bulgaria

Post by Mordred »

"evaluate" --> "enumerate" (right?)
These functions:

Code: Select all

$this->_getPHPCode(); 
      $this->_handleTags(); 
      $this->_stripComments(); 
      $this->_stripWhiteSpace(); 
      $this->_getCharacterCount(); 
      $this->_formatLineLength(); 
      $this->_getLineCount();
belong to phpsloc_file

All the classes are factories for the other classes, ideally they needn't/shouldn't be in a hierarchy.

It seems to me (but it's your choice in the end) that phpsloc_directory and phpsloc_directory_tree should be one class with a flag whether to work recursively on directories or not (work with opendir/readdir instead of glob for files) -- for every subdirectory you create the same class to work on it, and for every file you create phpsloc_file.

There are also minor issues with performance, but I guess that's not what you're looking after right now ;)

(btw whatever happened with the template engine?)
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

Mordred wrote:"evaluate" --> "enumerate" (right?)
These functions:

Code: Select all

$this->_getPHPCode(); 
      $this->_handleTags(); 
      $this->_stripComments(); 
      $this->_stripWhiteSpace(); 
      $this->_getCharacterCount(); 
      $this->_formatLineLength(); 
      $this->_getLineCount();
belong to phpsloc_file
You're absolutely right.
All the classes are factories for the other classes, ideally they needn't/shouldn't be in a hierarchy.
I don't know what "factories means". But I suppose you mean they build upon the parent class? If so, how come.. what's wrong with it? (that sounds like i'm saying you're wrong, but it's a sincere "what's wrong with it")
It seems to me (but it's your choice in the end) that phpsloc_directory and phpsloc_directory_tree should be one class with a flag whether to work recursively on directories or not (work with opendir/readdir instead of glob for files)
For now, I'll leave them as separate classes. Perhaps in my next version update I'll combine them. And, right. glob() invokes the regex engine, does it not? I think this falls in the performance category, though.
for every subdirectory you create the same class to work on it, and for every file you create phpsloc_file.
That's what I'm doing. Unless you mean I shouldn't do that.
There are also minor issues with performance, but I guess that's not what you're looking after right now ;)

(btw whatever happened with the template engine?)
:lol: I'll tackle performance after my code stops sucking. :(

The template engine is on hold till I feel like tackling nested blocks again..... :evil:

Right now for my next version update I'm working on using the tokenizer functions to strip comments, gather php blocks, and replace blank lines. And I'm scratching my unique way of counting lines for the following ways of counting lines (letting the user decide)

COUNT_SOFT - the way it is, every blank line, comment, and brace counts
COUNT_NO_COMMENTS - comments only removed
COUNT_NO_BLANKS - blank lines only removed
COUNT_NO_BRACE - curly braces on one line removed
COUNT_HARD - comments, blank lines, and curly braces removed

Thanks a lot for your input! I'd like to have a solid, well written library by time I'm done with it. Even if nobody ever uses it, I can just tell my buddies "yeah, i wrote that", knowing that it's super cool and have the feeling I could throw it against a brick wall and it wouldn't break. Know what I mean?
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

I love source code metrics :)

I use sloccount daily to measure my progress. Have you looked into how he calculates metrics, scottayy?
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

Here's an updated version :)

I got rid of regex's and used tokenizer to evaluate the source code. I also introduced 5 line counting modes for users to use and scratched the previous way of counting them..

COUNT_SOFT
- Lines are counted the way they appear in the code. All comments, blank lines, and curly braces on a single line are counted.

COUNT_NO_COMMENT
- Lines are counted after all comments are removed. Blank lines and curly braces on a single line are included in the line count.

COUNT_NO_BLANK
- Lines are counted after all blank lines are removed. Comments and curly braces on a single line are included in the line count.

COUNT_NO_BRACE
- Lines are counted after all lines containing a single curly brace are removed. Comments and blank lines are included in the line count.

COUNT_HARD
- Lines are counted after all comments, blank lines, and curly braces on a single line are removed.

phpsloc.class.php

Code: Select all

<?php

/**
 * phpsloc will allow you to evaluate any php script to determine the number of
 * source lines of code and characters in that script.  You can decide whether
 * or not to count HTML not encapsulated in php blocks as code (defaults to not
 * count it.. counts php only).
 *
 * Use one of the counting modes to determine how you want to count lines of
 * code in your scripts.
 *
 * The available counting modes are as follows:
 *
 * COUNT_SOFT
 *  - Lines are counted the way they appear in the code.  All comments, blank
 *    lines, and curly braces on a single line are counted.
 *
 * COUNT_NO_COMMENT
 *  - Lines are counted after all comments are removed.  Blank lines and curly
 *    braces on a single line are included in the line count.
 *
 * COUNT_NO_BLANK
 *  - Lines are counted after all blank lines are removed.  Comments and curly
 *    braces on a single line are included in the line count.
 *
 * COUNT_NO_BRACE
 *  - Lines are counted after all lines containing a single curly brace are
 *    removed.  Comments and blank lines are included in the line count.
 *
 * COUNT_HARD
 *  - Lines are counted after all comments, blank lines, and curly braces on a
 *    single line are removed.
 *
 * @Version 1.0.0
 *     - Initial Release
 *
 * @Version 1.0.1
 *     - Added # style comments to be removed
 *     - If _PHPOnly is set to false, HTML style comments will be removed
 *     - replaced php opening/closing tags with str_ireplace() to avoid
 *       potential casing issues.
 *     - Added a _returnHTML class member to return HTML instead of passing it
 *       as a parameter to the getStats() method.
 *     - Added a setter method setReturnHTML() to set the desired output style
 *
 * @Version 1.1.1
 *     - Broke the main class up into 4 classes, phpsloc() base class,
 *       phpsloc_file(), phpsloc_directory() and phpsloc_directoryTree()
 *     - Allows for single directory, and a directory tree parsing for files
 *     - Base class phpsloc() method getStats() has different parameters and
 *       instantiates a new object based on the $input parameter.
 *
 * @Version 1.2.1
 *     - Moved methods _getPHPCode(), _stripWhiteSpace(), _stripComments(), and
 *       _getCharacterCount() from class phpsloc() to class phpsloc_file().
 *     - Got rid of the method _formatLineLength()
 *     - Got rid of phpsloc() properties _lineLength, _allowSingleSpaces, and
 *       _allowPHPTags and their correspending setter methods.
 *     - Used the php tokenizer functions for parsing PHP code instead of
 *       regexes.
 *     - Got rid of the counting lines by linelengths of hard code.  That was
 *       ugly.
 *     - Got rid of glob() for finding php files
 *     - Introduced counting modes COUNT_SOFT, COUNT_NO_COMMENT, COUNT_NO_BLANK
 *       , COUNT_NO_BRACE, and COUNT_HARD, and setCountMode() method to set the
 *       counting mode.
 *
 *
 * @author Scott Martin <smp_info[at]yahoo[dot]com>
 * @date started November 27th, 2007
 * @last updated November 30th, 2007
 */
class phpsloc
{
	/**
	 * Container for each files code source
	 */
	protected $_code;

	/**
	 * Evaluate PHP only code
	 */
	protected $_PHPOnly = true;

	/**
	 * Whether to return HTML or not.  The default return is an array
	 * Set to true to return HTML.
	 */
	protected $_returnHTML = false;

	/**
	 * If _returnHTML is set to true, this boolean determines whether to send a
	 * full HTML page with headers or not.
	 */
	protected $_returnHTMLFull = true;

	/**
	 * An array of the counting modes available, and their description
	 */
	protected $_countModes = array(
		'COUNT_SOFT' => 'Lines are counted the way they appear in the code.  All
			comments, blank lines, and curly braces on a single line are counted.',

		'COUNT_NO_COMMENT' => 'Lines are counted after all comments are removed.
			Blank lines and curly braces on a single line are included in the line
			count.',

		'COUNT_NO_BLANK' => 'Lines are counted after all blank lines are removed.
			Comments and curly braces on a single line are included in the line count.',

		'COUNT_NO_BRACE' => 'Lines are counted after all lines containing a single curly
			brace are removed.  Comments and blank lines are included in the line
			count.',

		'COUNT_HARD' => 'Lines are counted after all comments, blank lines, and curly
			braces on a single line are removed.'
	);

	/**
	 * Holds the count mode used
	 */
	protected $_countMode;

	/**
	 * Set to true to only include php code, false to include all code in file
	 * contents (html, css, js, etc)
	 * @param boolean $bool - true to only parse php code, false to parse all code
	 * @access public
	 */
	public function setPHPOnly($bool)
	{
		$this->_PHPOnly = (bool) $bool;
	}

	/**
	 * Set the number of decimal points to round the lines of code to
	 * @param integer $int
	 * @access public
	 */
	public function setLineCountPrecision($int)
	{
		$this->_lineCountPrecision = (int) $int;
	}

	/**
	 * Set to return HTML instead of the default array returned
	 * @param boolean $bool - true to return html, false to return array
	 * @access public
	 */
	public function setReturnHTML($bool)
	{
		$this->_returnHTML = (bool) $bool;
	}

	/**
	 * Set to false to return only the body of the html generated, or set to true
	 * to return a full HTML page with headers.
	 * @param boolean $bool
	 * @access public
	 */
	public function setReturnHTMLFull($bool)
	{
		$this->_returnHTMLFull = (bool) $bool;
	}

	/**
	 * Sets the count mode to use for counting
	 * @param string $countMode
	 * @access public
	 */
	public function setCountMode($countMode)
	{
		if (in_array(strtoupper($countMode), array_keys($this->_countModes)))
		{
			$this->_countMode = strtoupper($countMode);
		} else
		{
			trigger_error(
				'phpsloc: Invalid count mode.  Valid count modes are COUNT_SOFT,
				COUNT_NO_COMMENT, COUNT_NO_BLANK, COUNT_NO_BRACE, and COUNT_HARD.',
				E_USER_ERROR
			);
		}
	}

	/**
	 * Instantiates a new object, generating data, then returns it
	 * @param string $input - the file or directory to be evaluated
	 * @param boolean $recurse - if $input is a directory, whether or not to
	 * recurse the directory tree.
	 * @return mixed - array when _returnHTML is false, string when true
	 * @access public
	 */
	public function getStats($input, $recurse=false)
	{
		if ($this->_countMode == NULL)
		{
			trigger_error(
				'phpsloc: No counting mode found.  Use setCountMode() with a parameter of
				COUNT_SOFT, COUNT_NO_COMMENT, COUNT_NO_BLANK, COUNT_NO_BRACE, or
				COUNT_HARD.',
				E_USER_ERROR
			);
		}

		if (is_file($input))
		{
			$ret = new phpsloc_file($input);
		} elseif (is_dir($input) && !$recurse)
		{
			$ret = new phpsloc_directory($input);
		} elseif (is_dir($input) && $recurse)
		{
			$ret = new phpsloc_directoryTree($input);
		} else
		{
			trigger_error(
				'PHPsloc: Could not evaluate input file or directory',
				E_USER_ERROR
			);
		}

		//set object properties
		$ret->_returnHTML = $this->_returnHTML;
		$ret->_returnHTMLFull = $this->_returnHTMLFull;
		$ret->_PHPOnly = $this->_PHPOnly;
		$ret->_countMode = $this->_countMode;

		return $ret->_getStat();
	}

	/**
	 * Generates HTML for output
	 * @param array $arr - array of generated stats
	 * @return string
	 * @access protected
	 */
	protected function _generateHTML($arr)
	{
		$htmlOutput = '';
		foreach ($arr AS $k => $v)
		{
			//$htmlOutput .= '';
			if (is_array($v))
			{
				$htmlOutput .= '<p><strong>' . str_replace('_', ' ', $k) . '</strong></p>';
				$htmlOutput .=  $this->_generateHTML($v, false);
			} else
			{
				$htmlOutput .=  !is_numeric($k) ?
					str_replace('_', ' ', $k) . ': <strong>' . $v . '</strong><br>'
					:
					'<strong>' . $v . '</strong><br>';
			}
		}
		return $htmlOutput;
	}

	/**
	 * Generates a standards compliant HTML header for HTML output.
	 * @param string $type - the type of evaluation being done
	 * @access protected
	 */
	protected function _HTMLHeader($type)
	{
		return '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
		   "http://www.w3.org/TR/html4/loose.dtd">
		<html>
		<head>
		<title>phpsloc ' . $type . ' Evaluation</title>
		<meta http-equiv="Content-type" content="text/html;UTF-8">
		<style type="text/css">
		body
		{
			background-color: #fff;
			color: #000;
			font-family: "courier new", courier, verdana, arial;
			font-size: 13px;
		}
		</style>
		</head>
		<body>';
	}

	/**
	 * Closes standards compliant full HTML output
	 * @ access protected
	 */
	protected function _HTMLFooter()
	{
		return '</body>
		</html>';
	}

}





/**
 * This is the class that deals with a single file.  It will evaluate only a
 * single file.  It can be used on single file input, directory input, or
 * directory tree input.
 */
class phpsloc_file extends phpsloc
{
	/**
	 * When the last line doesn't reach $_lineLength characters, how many decimal
	 * places should we round to?
	 */
	protected $_lineCountPrecision = 2;

	/**
	 * Holds the line count of the file after this class has prepared it
	 */
	protected $_lineCount;

	/**
	 * Holds the character count of the file's code
	 */
	protected $_characterCount;
	
	/**
	 * The name of the file to be evaluated
	 */
	private $_file;

	/**
	 * Constructor to set this class's $_file property, and parent class $_code
	 * property
	 * @param $file - string of file name
	 * @access protected
	 */
	protected function __construct($file)
	{
		$this->_file = $file;
		$this->_code = file_get_contents($file);
	}

	/**
	 * Runs through class methods generating stats and returns them.  Either as
	 * HTML or an array.
	 * @access protected
	 */
	protected function _getStat()
	{
		$this->_getPHPCode();
		
		//perform methods based on count mode
		switch ($this->_countMode)
		{
			case 'COUNT_SOFT':
			//do not modify the source code
			break;
			
			case 'COUNT_NO_COMMENT':
			$this->_stripComments();
			break;
			
			case 'COUNT_NO_BLANK':
			$this->_stripWhiteSpace();
			break;
			
			case 'COUNT_NO_BRACE':
			$this->_stripBraceOnly();
			break;
			
			case 'COUNT_HARD':
			$this->_stripComments();
			$this->_stripWhiteSpace();
			$this->_stripBraceOnly();
			break;
		}
		
		$this->_getCharacterCount();
		$this->_getLineCount();
		
		//write the return array
		$ret = array(
			'phpsloc_Configuration' => array(
				'PHP_Code_Only' => $this->_PHPOnly ? 'Yes' : 'No',
				'Line_Count_Float_Precision' => $this->_lineCountPrecision,
				'Mode_Used' => 'Single File',
				'Count_Mode_Used' => $this->_countMode,
				'Count_Mode_Description' => $this->_countModes[$this->_countMode]
			),

			'Code_Stats' => array(
				'File_Evaluated' => $this->_file,
				'Lines_Of_Code' => $this->_lineCount,
				'Characters_In_Code' => $this->_characterCount
			)
		);

		//if HTML is the preferred method of return, return it
		if ($this->_returnHTML)
		{
			if ($this->_returnHTMLFull)
			{
				return print
					$this->_HTMLHeader('File') .
					$this->_generateHTML($ret) .
					$this->_HTMLFooter();
			} else
			{
				return print $this->_generateHTML($ret);
			}
		}

		//just return the array
		return $ret;
	}

	/**
	 * Gathers php blocks from code
	 * @access private
	 */
	private function _getPHPCode()
	{
		//target file may have different line
		//endings - replace them all with a unified \n
		$this->_code = str_replace(array("\r\n", "\r"), "\n", $this->_code);
		
		//if user wants php only, rebuild the source code from tokens
		//we'll never miss any php code this way
		if ($this->_PHPOnly)
		{
			//suppress errors in case the code has a parse error
			$tokens = @token_get_all($this->_code);

			//initialize blocks array
			$blocks = array();

			//loop through each token
			foreach ($tokens AS $token)
			{
				if (!is_string($token))
				{
					//token id and text
					list($id, $text) = $token;

					//if it's not HTML, capture it
					if ($id != T_INLINE_HTML)
					{
						$blocks[] = $text;
					}
				} else
				{
					//capture string
					$blocks[] = $token;
				}
			}

			//get each block of php into a string
			$final = array();
			$i = 0;
			foreach ($blocks AS $blockLine)
			{
				//would love to use PHP_EOL here, but files come from different systems
				//however, when rebuilding the code, this library will just use \n
				//to get some unification going on
				if (($blockLine != "\r\n") && ($blockLine != "\r"))
				{
					if (isset($final[$i]))
					{
						$final[$i] .= $blockLine;
					} else
					{
						$final[$i] = $blockLine;
					}
				} else
				{
					$i++;
					$final[$i] = "\n";
				}
			}

			//get all blocks into a single string
			$this->_code = '';
			foreach ($final AS $f)
			{
				$this->_code .= $f;
			}
		}
	}

	/**
	 * Strips the files contents of lines containing only white space
	 * @access private
	 */
	private function _stripWhiteSpace()
	{
		//get each line
		$lines = explode("\n", $this->_code);

		//output container
		$output = array();

		//loop
		foreach ($lines AS $line)
		{
			//add trimmed line to output
			$output[] = trim($line);
		}

		//set code
		$this->_code = implode("\n", array_filter($output));
	}

	/**
	 * Strips the files contents of comments
	 * @todo make sure the comment isn't inside of a string
	 * @access private
	 */
	private function _stripComments()
	{
		$tokens = token_get_all($this->_code);
		$this->_code = '';

		foreach ($tokens AS $token)
		{
			if (is_string($token))
			{
				$this->_code .= $token;
			} else
			{
				list($id, $text) = $token;

				switch ($id)
				{
					case T_COMMENT:
					case T_DOC_COMMENT:
					break;

					default:
					$this->_code .= $text;
					break;
				}
			}
		}
	}

	private function _stripBraceOnly()
	{
		$lines = explode("\n", $this->_code);
		$output = array();

		foreach ($lines AS $line)
		{
			if (trim($line) !== '{' && trim($line) != '}')
			{
				$output[] = $line;
			}
		}

		$this->_code = implode("\n", $output);
	}

	/**
	 * Counts the characters in the phpsloc formatted code
	 * @access private
	 */
	private function _getCharacterCount()
	{
		$this->_characterCount = strlen($this->_code);
	}

	/**
	 * Counts the number of lines in the phpsloc formatted code, taking into
	 * consideration the last line.  If it is not a "full" line, it will be
	 * represented as a float value
	 * @access private
	 */
	private function _getLineCount()
	{
		$this->_lineCount = count(explode("\n", $this->_code));
	}
}





/**
 * This class evaluated a single directory, and is also used in recursive
 * directory trees.  Each file found in the directory is passed to
 * phpsloc_file() for evaluating each individual file.
 */
class phpsloc_directory extends phpsloc_file
{
	/**
	 * Holds the directory to be evaluated
	 */
	private $_directory;

	/**
	 * Holds the array of php files found in the directory
	 */
	private $_foundFiles;

	/**
	 * Constructor method sets the directory to be used
	 * @param string $directory
	 * @access protected
	 */
	protected function __construct($directory)
	{
		if (substr($directory, -1) == DIRECTORY_SEPARATOR)
		{
			$this->_directory = substr($directory, 0, strlen($directory-1));
		} else
		{
			$this->_directory = $directory;
		}
	}

	/**
	 * Runs through methods in this class, ultimately returning stats.
	 * @access protected
	 * @return mixed - array or string (depending on settings)
	 */
	protected function _getStat()
	{
		//find the files
		$this->_findFiles();

		//if HTML is the preferred return method, return it
		if ($this->_returnHTML)
		{
			if ($this->_returnHTMLFull)
			{
				//return html with headers
				return print $this->_HTMLHeader('Directory') . $this->_generateHTML(
						$this->_compoundSingleStats(
							$this->_evaluateSingleFiles()
						)
					) . $this->_HTMLFooter();
			} else
			{
				//return html without headers
				return print $this->_generateHTML(
						$this->_compoundSingleStats(
							$this->_evaluateSingleFiles()
						)
					);
			}
		}

		//return the array
		return $this->_compoundSingleStats($this->_evaluateSingleFiles());
	}

	/**
	 * Grabs all of the php files found in this directory and stores the array in
	 * class member.
	 * @access private
	 */
	private function _findFiles()
	{
		$found = array();
		if ($handle = opendir($this->_directory))
		{
			while (($file = readdir($handle)) !== false)
			{
				if (($file != '.') && ($file != '..'))
				{
					if (is_file($this->_directory . DIRECTORY_SEPARATOR . $file) && 
						(strtolower(substr($file, -4)) == '.php')
					)
					{
						$found[] = $this->_directory . DIRECTORY_SEPARATOR . $file;
					}
				}
			}
		} else
		{
			trigger_error(
				'phpsloc: Could not open directory (' . $this->_directory . ')',
				E_USER_WARNING
			);
		}
		
		$this->_foundFiles = $found;
	}

	/**
	 * Loops through each found file and creates a new phpsloc_file() object.
	 * Stores the returned array of stats in return value, then returns it.
	 * @return array
	 * @access private
	 */
	private function _evaluateSingleFiles()
	{
		//if we have files
		if (!empty($this->_foundFiles))
		{
			//loop through, gather single stats
			$ret = array();
			foreach ($this->_foundFiles AS $file)
			{
				$single = new phpsloc_file($file);
				$single->_returnHTML = false;
				$single->_PHPOnly = $this->_PHPOnly;
				$single->_lineCountPrecision = $this->_lineCountPrecision;
				$single->_countMode = $this->_countMode;
				$ret[] = $single->_getStat();
			}
		} else
		{
			//no files, return empty array
			$ret = array();
		}

		//return array of found file stats
		return $ret;
	}

	/**
	 * Compounds each files single stats into an array of stats for the
	 * directory.  This is really ugly at the moment.
	 * @param array $stats
	 * @return array
	 */
	private function _compoundSingleStats($stats)
	{
		//if we have stats
		if (!empty($stats))
		{
			//set up return array
			$ret['phpsloc_Configuration'] = $stats[0]['phpsloc_Configuration'];
			$ret['phpsloc_Configuration']['Mode_Used'] = 'Directory';
			$ret['Code_Stats']['Directory_Evaluated'] = '';
			$ret['Code_Stats']['Files_Evaluated'] = array();
			$ret['Code_Stats']['Summary'] = array();
			$ret['Code_Stats']['Summary']['Lines_Of_Code'] = 0;
			$ret['Code_Stats']['Summary']['Characters_In_Code'] = 0;
			$ret['Code_Stats']['Average'] = array();
			$ret['Code_Stats']['Average']['Lines_Of_Code'] = 0;
			$ret['Code_Stats']['Average']['Characters_In_Code'] = 0;
			$ret['Code_Stats']['Single_File_Stats'] = array();

			//loop through each, setting and adding stats
			$i = 0;
			foreach ($stats AS $stat)
			{
				if ($i == 0)
				{
					$ret['Code_Stats']['Directory_Evaluated'] = implode(
						DIRECTORY_SEPARATOR,
						array_diff(
							explode(
								DIRECTORY_SEPARATOR, $stat['Code_Stats']['File_Evaluated']
							),
							array(
								array_pop(
									explode(
										DIRECTORY_SEPARATOR, $stat['Code_Stats']['File_Evaluated']
									)
								)
							)
						)
					);
				}

				$ret['Code_Stats']['Files_Evaluated'][] =
					$stat['Code_Stats']['File_Evaluated'];

				$ret['Code_Stats']['Summary']['Lines_Of_Code'] +=
					$stat['Code_Stats']['Lines_Of_Code'];

				$ret['Code_Stats']['Summary']['Characters_In_Code'] +=
					$stat['Code_Stats']['Characters_In_Code'];

				$ret['Code_Stats']['Single_File_Stats'][] = array(
					'File' => $stat['Code_Stats']['File_Evaluated'],
					'Lines_Of_Code' => $stat['Code_Stats']['Lines_Of_Code'],
					'Characters_In_Code' => $stat['Code_Stats']['Characters_In_Code']
				);

				$i++;
			}

			//here we will get directory averages
			$ret['Code_Stats']['Average']['Lines_Of_Code'] =
				round(
					$ret['Code_Stats']['Summary']['Lines_Of_Code']
					/
					count($stats), $this->_lineCountPrecision
				);

			$ret['Code_Stats']['Average']['Characters_In_Code']
				= round(
					$ret['Code_Stats']['Summary']['Characters_In_Code']
					/
					count($stats), $this->_lineCountPrecision
				);
		} else
		{
			//we have nothing
			return array();
		}

		//return
		return $ret;
	}
}





class phpsloc_directoryTree extends phpsloc_directory
{
	/**
	 * Holds the root directory of the directory tree
	 */
	private $_directory;

	/**
	 * Sets the root directory of the directory tree
	 * @param string $directory
	 * @access protected
	 */
	protected function __construct($directory)
	{
		if (substr($directory, -1) == DIRECTORY_SEPARATOR)
		{
			$this->_directory = substr($directory, 0, strlen($directory-1));
		} else
		{
			$this->_directory = $directory;
		}
	}

	/**
	 * Generates stats and returns them
	 * @access protected
	 */
	protected function _getStat()
	{
		//find the directories
		$directories = $this->_findDirectories($this->_directory);

		//unshift root directory onto the beginning
		array_unshift($directories, $this->_directory);

		//get the stats
		$ret = $this->_compoundDirectories($this->_evaluateDirectories($directories));

		//return
		if ($this->_returnHTML)
		{
			if ($this->_returnHTMLFull)
			{
				return print
					$this->_HTMLHeader('Directory Tree') .
					$this->_generateHTML($ret) .
					$this->_HTMLFooter();
			}

			return print $this->_generateHTML($ret);
		}

		return $ret;
	}

	/**
	 * Compounds directory stats into a single array
	 * @param array $stats
	 * @access private
	 */
	private function _compoundDirectories($stats)
	{
		if (!empty($stats))
		{
			//set up return array
			$ret['phpsloc_Configuration'] = $stats[0]['phpsloc_Configuration'];
			$ret['phpsloc_Configuration']['Mode_Used'] = 'Directory Tree';
			$ret['Code_Stats']['Directory_Tree_Evaluated'] = $this->_directory;
			$ret['Code_Stats']['Directories_Evaluated'] = array();
			$ret['Code_Stats']['Files_Evaluated'] = array();
			$ret['Code_Stats']['Summary'] = array();
			$ret['Code_Stats']['Summary']['Lines_Of_Code'] = 0;
			$ret['Code_Stats']['Summary']['Characters_In_Code'] = 0;
			$ret['Code_Stats']['Average_Per_Directory'] = array();
			$ret['Code_Stats']['Average_Per_Directory']['Lines_Of_Code'] = 0;
			$ret['Code_Stats']['Average_Per_Directory']['Characters_In_Code'] = 0;
			$ret['Code_Stats']['Average_Per_File'] = array();
			$ret['Code_Stats']['Average_Per_File']['Lines_Of_Code'] = 0;
			$ret['Code_Stats']['Average_Per_File']['Characters_In_Code'] = 0;
			$ret['Code_Stats']['Single_File_Stats'] = array();

			foreach ($stats AS $stat)
			{
				$ret['Code_Stats']['Directories_Evaluated'][] =
					$stat['Code_Stats']['Directory_Evaluated'];

				$ret['Code_Stats']['Files_Evaluated'] =
					array_merge(
						$ret['Code_Stats']['Files_Evaluated'],
						$stat['Code_Stats']['Files_Evaluated']
					);

				$ret['Code_Stats']['Summary']['Lines_Of_Code'] +=
					$stat['Code_Stats']['Summary']['Lines_Of_Code'];

				$ret['Code_Stats']['Summary']['Characters_In_Code'] +=
					$stat['Code_Stats']['Summary']['Characters_In_Code'];

				$ret['Code_Stats']['Single_File_Stats'] =
					array_merge(
						$ret['Code_Stats']['Single_File_Stats'],
						$stat['Code_Stats']['Single_File_Stats']
					);
			}

			//here we will get directory averages
			$ret['Code_Stats']['Average_Per_Directory']['Lines_Of_Code'] =
				round(
					$ret['Code_Stats']['Summary']['Lines_Of_Code']
					/
					count($stats), $this->_lineCountPrecision
				);

			$ret['Code_Stats']['Average_Per_Directory']['Characters_In_Code']
				= round(
					$ret['Code_Stats']['Summary']['Characters_In_Code']
					/
					count($stats),
					$this->_lineCountPrecision
				);

			//here we will get single file averages
			$ret['Code_Stats']['Average_Per_File']['Lines_Of_Code'] =
				round(
					$ret['Code_Stats']['Summary']['Lines_Of_Code']
					/
					count($ret['Code_Stats']['Files_Evaluated']),
					$this->_lineCountPrecision
				);

			$ret['Code_Stats']['Average_Per_File']['Characters_In_Code']
				= round(
					$ret['Code_Stats']['Summary']['Characters_In_Code']
					/
					count($ret['Code_Stats']['Files_Evaluated']),
					$this->_lineCountPrecision
				);
		} else
		{
			//we have nothing
			return array();
		}

		return $ret;
	}

	/**
	 * Evaluate each single directory
	 * @param array $directories
	 */
	private function _evaluateDirectories($directories)
	{
		//echo '<pre>';print_r($directories);echo'</pre>';
		$ret = array();
		foreach ($directories AS $directory)
		{
			//instantiate new directory object
			$dirObj = new phpsloc_directory($directory);

			//set object properties
			$dirObj->_returnHTML = false;
			$dirObj->_PHPOnly = $this->_PHPOnly;
			$dirObj->_lineCountPrecision = $this->_lineCountPrecision;
			$dirObj->_countMode = $this->_countMode;

			//add to ret array if not empty
			$stat = $dirObj->_getStat();
			if (!empty($stat))
			{
				$ret[] = $stat;
			}
		}

		return $ret;
	}

	/**
	 * Recursively find directories
	 * @param $start
	 * @access private
	 */
	private function _findDirectories($start)
	{
		$ret = array();
		$handle = opendir($start);
		while (($file = readdir($handle)) !== false)
		{
			$file = $start . DIRECTORY_SEPARATOR . $file;
			if (
				($file != $start . DIRECTORY_SEPARATOR . '.') &&
				($file != $start . DIRECTORY_SEPARATOR . '..')
			)
			{
				if (is_dir($file))
				{
					array_push($ret, $file);
					$ret = array_merge($ret, $this->_findDirectories($file));
				}
			}
		}

		return $ret;
	}


}
Single File

Code: Select all

//single file
$phpsloc = new phpsloc();
$phpsloc->setCountMode('COUNT_HARD');
$stats = $phpsloc->getStats('c:\users\scott\desktop\db.mysql.php');

echo '<pre>';
print_r($stats);
echo '</pre>';
Which turned my test db class into

Code: Select all

<?php
class db
private $_link;
private $_queryCount = 0;
public function connect($host, $username, $password, $newLink=null, $flags=null)
$this->_link = mysql_connect($host, $username, $password, $newLink, $flags);
return $this->_link;
public function pConnect($host, $username, $password, $flags=null)
$this->_link = mysql_pconnect($host, $username, $password, $flags);
return $this->_link;
public function ping()
return mysql_ping($this->_link);
public function status()
return mysql_stat($this->_link);
public function getThreadId()
return mysql_thread_id($this->_link);
public function selectDb($dbName)
return mysql_select_db($dbName, $this->_link);
public function createDb($dbName)
return mysql_create_db($dbName, $this->_link);
public function escape($item)
return mysql_real_escape_string($item, $this->_link);
public function query($sql)
if ($result = mysql_query($sql) && is_resource($result))
$this->_queryCount++;
return new dbResult($result);
} else
return false;
public function unbufferedQuery($sql)
if ($result = mysql_unbuffered_query($sql) && is_resource($result))
$this->_queryCount++;
return new dbResult($result);
} else
return false;
public function insertId()
return mysql_insert_id($this->_link);
public function affectedRows()
return mysql_affected_rows($this->_link);
public function getCharset()
return mysql_client_encoding($this->_link);
public function setCharset($charset)
return mysql_set_charset($charset, $this->_link);
public function getClientInfo()
return mysql_get_client_info();
public function getHostInfo()
return mysql_get_host_info($this->_link);
public function getProtoInfo()
return mysql_get_proto_info($this->_link);
public function getServerInfo()
return mysql_get_server_info($this->_link);
public function info()
return mysql_info($this->_link);
public function errorNumber()
return mysql_errno($this->_link);
public function error()
return mysql_error($this->_link);
public function numQueries()
return $this->_queryCount;
public function close()
return mysql_close($this->_link);
class dbResult
private $_result;
public function __construct($result)
$this->_result = $result;
public function fetch($type=MYSQL_ASSOC)
return mysql_fetch_assoc($this->_result, $type);
public function fetchRow()
return mysql_fetch_row($this->_result);
public function fetchField($offset=0)
return mysql_fetch_field($this->_result, $offset);
public function fetchLengths()
return mysql_fetch_lengths($this->_result);
public function fetchObject($className=null, $params=null)
return mysql_fetch_object($this->_result, $className, $params);
public function fieldFlags($offset)
return mysql_field_flags($this->_result, $offset);
public function fieldLength($offset=0)
return mysql_field_len($this->_result, $offset);
public function fieldName($offset=0)
return mysql_field_name($this->_result, $offset);
public function fieldSeek($offset)
return mysql_field_seek($this->_result, $offset);
public function fieldTable($offset)
return mysql_field_table($this->_result, $offset);
public function fieldType($offset)
return mysql_field_type($this->_result, $offset);
public function dbName($row, $field=null)
return mysql_db_name($this->_result, $row, $field);
public function numRows()
return mysql_num_rows($this->_result);
public function numFields()
return mysql_num_fields($this->_result);
public function seek($row)
return mysql_data_seek($this->_result, $row);
public function free()
return mysql_free_result($this->_result);
Results

Code: Select all

Array
(
    [phpsloc_Configuration] => Array
        (
            [PHP_Code_Only] => Yes
            [Line_Count_Float_Precision] => 2
            [Mode_Used] => Single File
            [Count_Mode_Used] => COUNT_HARD
            [Count_Mode_Description] => Lines are counted after all comments, blank lines, and curly
			braces on a single line are removed.
        )

    [Code_Stats] => Array
        (
            [File_Evaluated] => c:\users\scott\desktop\db.mysql.php
            [Lines_Of_Code] => 96
            [Characters_In_Code] => 3457
        )

)
:) I'm feeling much better about the library.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

A couple problems I still have with the library. I very much dislike the idea of "modes". I prefer configuration and flexibility (I'll illustrate flexability below) :D. Two, being there is still very little seperation between the responsibility of your objects. For instance, I've written a LOC library and seperated the code into essentially three types: iterators, filters, and renderers.

A/Utility/Loc.php
A/Utility/Loc/Iterator.php
A/Utility/Loc/Iterator/Directory.php
A/Utility/Loc/Iterator/File.php
A/Utility/Loc/Iterator/Interface
A/Utility/Loc/Filter.php
A/Utility/Loc/Filter/RemoveBlank.php
A/Utility/Loc/Filter/RemoveComment.php
A/Utility/Loc/Filter/...
A/Utility/Loc/Filter/Interface
A/Utility/Loc/Render/Html.php
A/Utility/Loc/Render/Graphic.php
A/Utility/Loc/Render/CLI.php
A/Utility/Loc/Render/Interface.php

The iterators are responsible for fetching the required files to perform the LOC analysis, this includes recursively searching if specified.

The Filters will filter each individual file according to whichever filters you want to apply.. ie. removing whitespace. All of the filters are of course modifiable.

The renders will output the content into whichever format you want to display to the user (if any)


Heres an idea of what a much more flexible interface would look like (actually a copy+paste example from my library)

Code: Select all

//create the line counter object
$linecount = new A_Utility_Loc();
#$linecount->addFilter(new A_Utility_Loc_Filter_RemoveNonPHP);
$linecount->addFilter(new A_Utility_Loc_Filter_RemoveBlank);
$linecount->addFilter(new A_Utility_Loc_Filter_RemoveComment);
#$linecount->addFilter(new A_Utility_Loc_Filter_RemoveOpenBrace);

//A_Utility_Loc_Iterator_Directory accepts a 2nd boolean parameter to specify recursion
$linecount->addSource(new A_Utility_Loc_Iterator_Directory(getcwd(), true));
#$linecount->addSource(new A_Utility_Loc_Iterator_File('/path/to/file.php'));

echo $linecount->report(new A_Utility_Loc_Reporter_HTML);
#echo $linecount->report(new A_Utility_Loc_Reporter_Graphic);
#echo $linecount->report(new A_Utility_Loc_Reporter_CLI);
I see this as a great opportunity to refactor :D, so whatcha say?
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

Absolutely. This is a great opportunity to learn more OO practices. And produce something someone may actually use. I like the idea of refactoring, but I also like the idea of the count modes. Perhaps there's a middle ground there.. leave the count modes.. but allow the user to pass multiple combinations.. which would spawn those filters, as you call it. What do you think about that?

Could you explain a little bit about what an Interface is? I noticed that in your directory structure. I'm thinking the filter interface is sort of a controller that spawns the appropriate filter?

I'll work on refactoring for the next 1.x update.

Anyways, the nerdy side of me took over and I made a quick little web site for this. :) My inner geek has always wanted to have a web site page for a library that I wrote.. :( I don't know why. Does anybody else feel like that? Maybe I'm overdoing it for such a trivial task as counting lines of code, but hey, it's fun!

I googled phpsloc, and some code already has the name. So I'm changing the name to phpSoCo. I also just made this version 1.0.0.. since the updates made in the topic led up to a library that is at least somewhat efficient and coherent. So..


phpSoCo

Website: http://www.scottayy.com/phpsoco
Download: http://www.scottayy.com/phpsoco/download.php
Example Usage: http://www.scottayy.com/phpsoco/examples.php

=]
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

Could you explain a little bit about what an Interface is? I noticed that in your directory structure. I'm thinking the filter interface is sort of a controller that spawns the appropriate filter?
Nope, it is just a simple interface that all the filters must implement.

Code: Select all

interface A_Utility_Filter_Interface
{
	public function run($data);
}
like the idea of the count modes. Perhaps there's a middle ground there.. leave the count modes.. but allow the user to pass multiple combinations.. which would spawn those filters, as you call it. What do you think about that?
We'll I guess there is a difference of opinion, since it is not immediatly obvious what the different count modes do I would personally avoid them all together. Including a description like you did is one solution, although that is just extra output that really doesn't belong anyways.

Either way, if you wanted to continue down this path of modes.. currently if you wanted include a mode you could createa a new filter which would call the other filters.

Code: Select all

require 'RemoveNonPHP.php';
require 'RemoveBlanklines';
require 'RemoveOpeningBrace.php';
require 'Interface.php';

class A_Utility_Filter_HardCount implements A_Utility_Filter_Interface
{
   public function run($data) 
   {
      $filter = new A_Utility_Filter_RemoveNonPHP();
      $data = $filter->run($data);

      $filter = new A_Utility_Filter_RemoveBlanklines();
      $data = $filter->run($data);

      $filter = new A_Utility_Filter_RemoveOpeningBrace();
      $data = $filter->run($data);

      return $data;
   }
}
My point being, flexibility. This way, the user can inject any filters he wants, using your "modes" the user is directly fixed into specific formats.
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

You've actually sparked my interest finish/fix up my own library, and have decided to include different strategies towards the output.

This will certainly clean up your base class alot, as I see tons of different arrays in your code holding all the different stats. Seems like tag soup to me..

The idea behind this is you the user to be able to specify exactly what they want to display, and only calculate what is neccesary.

Code: Select all

$linecount->addCounter(new A_Utility_Loc_Counter_TotalFiles, 'Total Files');
$linecount->addCounter(new A_Utility_Loc_Counter_TotalLines, 'Total Lines');
$linecount->addCounter(new A_Utility_Loc_Counter_AverageLines, 'Average Lines');
$linecount->addCounter(new A_Utility_Loc_Counter_TotalCharacters, 'Total Chracters');
$linecount->addCounter(new A_Utility_Loc_Counter_AverageCharacters);
Each diffferent strategy will get an initial pass through a preCalculate() method to perform add the results from each of the files, then a second pass through a postCalculate() method is done once all the files have been summing the totals. The second pass will make any final calculates such as calculating the sum against the total files to get the average.

I think your first goal towards refactoring would be getting rid of how you handle the calculations to eliminate the tag soup. It simply makes it too dificult to modify the code..

Sorry for semi-hijacking your thread, let me know if you want to take a peak at some more of my code or thoughts.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

You didn't hijack my topic :P

You're helping me out with totally oopifying, which I appreciate a lot.
I like the ideas of added flexibility. Instead of just being an all-in-one solution, it lets the user decide exactly what they want.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

Wasn't planning on releasing the package yet, but I think it will give you a good idea on how to structure things a bit better.

You can download my package at: http://zodiac-zf.com/packages/loc.rar (also contains the example)
Or view a live example: http://dev.zodiac-zf.com/loc

Let me know if you have anymore questions.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

I'm looking through your code source. It's really "pretty", which is something I strive for in my code. It's tight, too. Well done!

One thing I want to mention is I noticed you're using PHP_EOL. I tried that in phpSoCo, and it worked fine for files that were developed on my machine (windows) or on another windows machine. But, for example, feyds sha256 library users unix line endings, and PHP_EOL didn't recognize that. My library reported 1 line of code with (example) 10,000 characters. What I did to bypass that was make line endings a unified \n.

Code: Select all

$source = str_replace(array("\r\n", "\r"), "\n", $source);
I haven't tried your example yet, I'm just ravaging through the source code.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

Look at my example again, I've fitted it with feyd's Sha256 library.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

That's interesting. I know in my implementation PHP_EOL did not work. I see your code has DOS line endings, but it recognized unix line endings. How is that possible?

Unless the sha256 library you loaded has DOS line endings.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
Post Reply