      1: <?php
      2:
      3: try{
      4:     AlphaAutoLoader::loadLib('Find.php');
      5: }catch (LibraryNotInstalledException $e) {
      6:     throw new LibraryNotInstalledException('Unable to generate code metrics as the PEAR::File_Find package is not installed!');
      7:     exit;
      8: }
      9:
     10: /**
     11:  * Utility class for calcualting some software metics related to a project
     12:  *
     13:  * @package alpha::util::metrics
     14:  * @since 1.0
     15:  * @author John Collins <dev@alphaframework.org>
     16:  * @version $Id: AlphaMetrics.inc 1756 2014-04-08 22:54:10Z alphadevx $
     17:  * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
     18:  * @copyright Copyright (c) 2014, John Collins (founder of Alpha Framework).
     19:  * All rights reserved.
     20:  *
     21:  * <pre>
     22:  * Redistribution and use in source and binary forms, with or
     23:  * without modification, are permitted provided that the
     24:  * following conditions are met:
     25:  *
     26:  * * Redistributions of source code must retain the above
     27:  *   copyright notice, this list of conditions and the
     28:  *   following disclaimer.
     29:  * * Redistributions in binary form must reproduce the above
     30:  *   copyright notice, this list of conditions and the
     31:  *   following disclaimer in the documentation and/or other
     32:  *   materials provided with the distribution.
     33:  * * Neither the name of the Alpha Framework nor the names
     34:  *   of its contributors may be used to endorse or promote
     35:  *   products derived from this software without specific
     36:  *   prior written permission.
     37:  *
     38:  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
     39:  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
     40:  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     41:  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     42:  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
     43:  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     44:  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     45:  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     46:  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     47:  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     48:  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
     49:  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     50:  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     51:  * </pre>
     52:  *
     53:  */
     54: class AlphaMetrics {
     55:     /**
     56:      * The directory to begin the calculations from
     57:      *
     58:      * @var string
     59:      * @since 1.0
     60:      */
     61:     private $rootDir;
     62:
     63:     /**
     64:      * The file extensions of the file types to include in the calculations
     65:      *
     66:      * @var array
     67:      * @since 1.0
     68:      */
     69:     private $includeFileTypes = array('.php', '.html', '.phtml', '.inc', '.js', '.css', '.xml');
     70:
     71:     /**
     72:      * Any sub-directories which you might want to exclude from the calculations
     73:      *
     74:      * @var array
     75:      * @since 1.0
     76:      */
     77:     private $excludeSubDirectories = array('cache','lib','docs','attachments','dist');
     78:
     79:     /**
     80:      * The Total Lines of Code (TLOC) for the project
     81:      *
     82:      * @var integer
     83:      * @since 1.0
     84:      */
     85:     private $TLOC = 0;
     86:
     87:     /**
     88:      * The Total Lines of Code (TLOC) for the project, less comments defined in $comments
     89:      *
     90:      * @var integer
     91:      * @since 1.0
     92:      */
     93:     private $TLOCLessComments = 0;
     94:
     95:     /**
     96:      * The count of the source code files in the project
     97:      *
     98:      * @var integer
     99:      * @since 1.0
    100:      */
    101:     private $fileCount = 0;
    102:
    103:     /**
    104:      * An array of fileName => lines of code to be populated by this class
    105:      *
    106:      * @var array
    107:      * @since 1.0
    108:      */
    109:     private $filesLOCResult = array();
    110:
    111:     /**
    112:      * An array of fileName => lines of code to be populated by this class,
    113:      * excluding comment lines defined in the $comments array
    114:      *
    115:      * @var array
    116:      * @since 1.0
    117:      */
    118:     private $filesLOCNoCommentsResult = array();
    119:
    120:     /**
    121:      * An array of the source code file names in the project
    122:      *
    123:      * @var array
    124:      * @since 1.0
    125:      */
    126:     private $files = array();
    127:
    128:     /**
    129:      * An array of the directories in the project
    130:      *
    131:      * @var array
    132:      * @since 1.0
    133:      */
    134:     private $directories = array();
    135:
    136:     /**
    137:      * An array of the first characters of a comment line in source code
    138:      *
    139:      * @var array
    140:      * @since 1.0
    141:      */
    142:     private $comments = array('/','*','#');
    143:
    144:     /**
    145:      * Constructor, default $rootDir is .
    146:      *
    147:      * @param string $rootDir
    148:      * @since 1.0
    149:      */
    150:     public function __construct($rootDir = '.') {
    151:         $this->rootDir = $rootDir;
    152:         // populate the file and directories arrays using the File_Find class
    153:         list($this->directories, $this->files) = File_Find::maptree($rootDir);
    154:     }
    155:
    156:     /**
    157:      * Calculates the Lines of Code (LOC)
    158:      *
    159:      * @since 1.0
    160:      */
    161:     public function calculateLOC() {
    162:         foreach ($this->files as $file) {
    163:             $file_type = mb_substr($file, mb_strrpos($file, '.'));
    164:             if (in_array($file_type, $this->includeFileTypes)) {
    165:                 $exclude = false;
    166:                 foreach ($this->excludeSubDirectories as $dir) {
    167:                     if (preg_match("/".$dir."/i", $file)) {
    168:                         $exclude = true;
    169:                     }
    170:                 }
    171:
    172:                 if (!$exclude) {
    173:                     $current_file = file($file);
    174:
    175:                     $LOC = count($current_file);
    176:                     $this->filesLOCResult[$file] = $LOC;
    177:                     $LOC_less_comments = $this->disregardCommentsLOC($file);
    178:                     $this->filesLOCNoCommentsResult[$file] = $LOC_less_comments;
    179:
    180:                     $this->TLOC += $LOC;
    181:                     $this->TLOCLessComments += $LOC_less_comments;
    182:                     $this->fileCount++;
    183:                 }
    184:             }
    185:         }
    186:     }
    187:
    188:     /**
    189:      * Generates a HTML table containing the metrics results
    190:      *
    191:      * @return string
    192:      * @since 1.0
    193:      */
    194:     public function resultsToHTML() {
    195:         $count = 1;
    196:
    197:         $html = '<table class="table table-striped table-hover"><tr>';
    198:         $html .= '<th style="width:10%;">File #:</th>';
    199:         $html .= '<th style="width:50%;">File name:</th>';
    200:         $html .= '<th style="width:20%;">Lines of Code (LOC):</th>';
    201:         $html .= '<th style="width:20%;">Lines of Code (less comments):</th>';
    202:         $html .= '</tr>';
    203:         foreach(array_keys($this->filesLOCResult) as $result) {
    204:             $html .= "<tr><td>$count</td><td>$result</td><td>".$this->filesLOCResult[$result]."</td><td>".$this->filesLOCNoCommentsResult[$result]."</td></tr>";
    205:             $count++;
    206:         }
    207:         $html .= '</table>';
    208:
    209:         $html .= "<p>Total files: ".number_format(count($this->files))."</p>";
    210:         $html .= "<p>Total source code files: ".number_format($this->fileCount)."</p>";
    211:         $html .= "<p>Total Lines of Code (TLOC): ".number_format($this->TLOC)."</p>";
    212:         $html .= "<p>Total Lines of Code (TLOC), less comments: ".number_format($this->TLOCLessComments)."</p>";
    213:
    214:         return $html;
    215:     }
    216:
    217:     /**
    218:      * Filters comments from LOC metric
    219:      *
    220:      * @param string $sourceFile
    221:      * @return integer
    222:      * @since 1.0
    223:      */
    224:     private function disregardCommentsLOC($sourceFile) {
    225:         $file = file($sourceFile);
    226:
    227:         $LOC = 0;
    228:
    229:         foreach ($file as $line) {
    230:             $exclude = false;
    231:             $line = ltrim($line);
    232:
    233:             if(empty($line)) {
    234:                 $exclude = true;
    235:             }else{
    236:                 foreach ($this->comments as $comment) {
    237:                     if (mb_substr($line, 0, 1) == $comment) {
    238:                         $exclude = true;
    239:                     }
    240:                 }
    241:             }
    242:
    243:             if (!$exclude) {
    244:                 $LOC++;
    245:             }
    246:         }
    247:
    248:         return $LOC;
    249:     }
    250: }
    251:
    252: ?>