You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by ch...@apache.org on 2008/06/06 16:56:03 UTC
svn commit: r663970 [22/25] - in /incubator/shindig/trunk/php: ./ test/
test/PHPUnit/ test/PHPUnit/Extensions/ test/PHPUnit/Extensions/Database/
test/PHPUnit/Extensions/Database/Constraint/
test/PHPUnit/Extensions/Database/DB/ test/PHPUnit/Extensions/D...
Added: incubator/shindig/trunk/php/test/PHPUnit/Util/Report/Node/File.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/test/PHPUnit/Util/Report/Node/File.php?rev=663970&view=auto
==============================================================================
--- incubator/shindig/trunk/php/test/PHPUnit/Util/Report/Node/File.php (added)
+++ incubator/shindig/trunk/php/test/PHPUnit/Util/Report/Node/File.php Fri Jun 6 07:55:55 2008
@@ -0,0 +1,842 @@
+<?php
+/**
+ * PHPUnit
+ *
+ * Copyright (c) 2002-2008, Sebastian Bergmann <sb...@sebastian-bergmann.de>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Sebastian Bergmann nor the names of his
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category Testing
+ * @package PHPUnit
+ * @author Sebastian Bergmann <sb...@sebastian-bergmann.de>
+ * @copyright 2002-2008 Sebastian Bergmann <sb...@sebastian-bergmann.de>
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version SVN: $Id: File.php 1985 2007-12-26 18:11:55Z sb $
+ * @link http://www.phpunit.de/
+ * @since File available since Release 3.2.0
+ */
+
+require_once '../PHPUnit/Util/Filter.php';
+require_once '../PHPUnit/Util/Filesystem.php';
+require_once '../PHPUnit/Util/Template.php';
+require_once '../PHPUnit/Util/Report/Node.php';
+
+PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT');
+
+/**
+ *
+ *
+ * @category Testing
+ * @package PHPUnit
+ * @author Sebastian Bergmann <sb...@sebastian-bergmann.de>
+ * @copyright 2002-2008 Sebastian Bergmann <sb...@sebastian-bergmann.de>
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version Release: 3.2.9
+ * @link http://www.phpunit.de/
+ * @since Class available since Release 3.2.0
+ */
+class PHPUnit_Util_Report_Node_File extends PHPUnit_Util_Report_Node
+{
+ /**
+ * @var array
+ * @access protected
+ */
+ protected $codeLines;
+
+ /**
+ * @var array
+ * @access protected
+ */
+ protected $codeLinesFillup = array();
+
+ /**
+ * @var array
+ * @access protected
+ */
+ protected $executedLines;
+
+ /**
+ * @var boolean
+ * @access protected
+ */
+ protected $yui = TRUE;
+
+ /**
+ * @var boolean
+ * @access protected
+ */
+ protected $highlight = FALSE;
+
+ /**
+ * @var integer
+ * @access protected
+ */
+ protected $numExecutableLines = 0;
+
+ /**
+ * @var integer
+ * @access protected
+ */
+ protected $numExecutedLines = 0;
+
+ /**
+ * @var array
+ * @access protected
+ */
+ protected $classes = array();
+
+ /**
+ * @var integer
+ * @access protected
+ */
+ protected $numClasses = 0;
+
+ /**
+ * @var integer
+ * @access protected
+ */
+ protected $numCalledClasses = 0;
+
+ /**
+ * @var integer
+ * @access protected
+ */
+ protected $numMethods = 0;
+
+ /**
+ * @var integer
+ * @access protected
+ */
+ protected $numCalledMethods = 0;
+
+ /**
+ * @var string
+ * @access protected
+ */
+ protected $yuiPanelJS = '';
+
+ /**
+ * Constructor.
+ *
+ * @param string $name
+ * @param PHPUnit_Util_Report_Node $parent
+ * @param array $executedLines
+ * @param boolean $yui
+ * @param boolean $highlight
+ * @throws RuntimeException
+ * @access public
+ */
+ public function __construct($name, PHPUnit_Util_Report_Node $parent = NULL, array $executedLines, $yui = TRUE, $highlight = FALSE)
+ {
+ parent::__construct($name, $parent);
+
+ $path = $this->getPath();
+
+ if (!file_exists($path)) {
+ throw new RuntimeException;
+ }
+
+ $this->executedLines = $executedLines;
+ $this->highlight = $highlight;
+ $this->yui = $yui;
+ $this->codeLines = $this->loadFile($path);
+
+ $this->calculateStatistics();
+ }
+
+ /**
+ * Returns the classes of this node.
+ *
+ * @return array
+ * @access public
+ */
+ public function getClasses()
+ {
+ return $this->classes;
+ }
+
+ /**
+ * Returns the number of executable lines.
+ *
+ * @return integer
+ * @access public
+ */
+ public function getNumExecutableLines()
+ {
+ return $this->numExecutableLines;
+ }
+
+ /**
+ * Returns the number of executed lines.
+ *
+ * @return integer
+ * @access public
+ */
+ public function getNumExecutedLines()
+ {
+ return $this->numExecutedLines;
+ }
+
+ /**
+ * Returns the number of classes.
+ *
+ * @return integer
+ * @access public
+ */
+ public function getNumClasses()
+ {
+ return $this->numClasses;
+ }
+
+ /**
+ * Returns the number of classes of which at least one method
+ * has been called at least once.
+ *
+ * @return integer
+ * @access public
+ */
+ public function getNumCalledClasses()
+ {
+ return $this->numCalledClasses;
+ }
+
+ /**
+ * Returns the number of methods.
+ *
+ * @return integer
+ * @access public
+ */
+ public function getNumMethods()
+ {
+ return $this->numMethods;
+ }
+
+ /**
+ * Returns the number of methods that has been called at least once.
+ *
+ * @return integer
+ * @access public
+ */
+ public function getNumCalledMethods()
+ {
+ return $this->numCalledMethods;
+ }
+
+ /**
+ * Renders this node.
+ *
+ * @param string $target
+ * @param string $title
+ * @param string $charset
+ * @param boolean $highlight
+ * @param integer $lowUpperBound
+ * @param integer $highLowerBound
+ * @access public
+ */
+ public function render($target, $title, $charset = 'ISO-8859-1', $highlight = FALSE, $lowUpperBound = 35, $highLowerBound = 70)
+ {
+ if ($this->yui) {
+ $template = new PHPUnit_Util_Template(
+ PHPUnit_Util_Report::$templatePath . 'file.html'
+ );
+
+ $yuiTemplate = new PHPUnit_Util_Template(
+ PHPUnit_Util_Report::$templatePath . 'yui_item.js'
+ );
+ } else {
+ $template = new PHPUnit_Util_Template(
+ PHPUnit_Util_Report::$templatePath . 'file_no_yui.html'
+ );
+ }
+
+ $i = 1;
+ $lines = '';
+ $ignore = FALSE;
+
+ foreach ($this->codeLines as $line) {
+ if (strpos($line, '@codeCoverageIgnore') !== FALSE) {
+ if (strpos($line, '@codeCoverageIgnoreStart') !== FALSE) {
+ $ignore = TRUE;
+ }
+
+ else if (strpos($line, '@codeCoverageIgnoreEnd') !== FALSE) {
+ $ignore = FALSE;
+ }
+ }
+
+ $css = '';
+
+ if (!$ignore && isset($this->executedLines[$i])) {
+ $count = '';
+
+ // Array: Line is executable and was executed.
+ // count(Array) = Number of tests that hit this line.
+ if (is_array($this->executedLines[$i])) {
+ $color = 'lineCov';
+ $numTests = count($this->executedLines[$i]);
+ $count = sprintf('%8d', $numTests);
+
+ if ($this->yui) {
+ $buffer = '';
+
+ foreach ($this->executedLines[$i] as $test) {
+ if (!isset($test->__liHtml)) {
+ $test->__liHtml = '';
+
+ if ($test instanceof PHPUnit_Framework_SelfDescribing) {
+ $testName = $test->toString();
+
+ if ($test instanceof PHPUnit_Framework_TestCase) {
+ switch ($test->getStatus()) {
+ case PHPUnit_Runner_BaseTestRunner::STATUS_PASSED: {
+ $testCSS = ' class=\"testPassed\"';
+ }
+ break;
+
+ case PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE: {
+ $testCSS = ' class=\"testFailure\"';
+ }
+ break;
+
+ case PHPUnit_Runner_BaseTestRunner::STATUS_ERROR: {
+ $testCSS = ' class=\"testError\"';
+ }
+ break;
+
+ case PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE:
+ case PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED: {
+ $testCSS = ' class=\"testIncomplete\"';
+ }
+ break;
+
+ default: {
+ $testCSS = '';
+ }
+ }
+ }
+ }
+
+ $test->__liHtml .= sprintf(
+ '<li%s>%s</li>',
+
+ $testCSS,
+ $testName
+ );
+ }
+
+ $buffer .= $test->__liHtml;
+ }
+
+ if ($numTests > 1) {
+ $header = $numTests . ' tests cover';
+ } else {
+ $header = '1 test covers';
+ }
+
+ $header .= ' line ' . $i;
+
+ $yuiTemplate->setVar(
+ array(
+ 'line' => $i,
+ 'header' => $header,
+ 'tests' => $buffer
+ ),
+ FALSE
+ );
+
+ $this->yuiPanelJS .= $yuiTemplate->render();
+ }
+ }
+
+ // -1: Line is executable and was not executed.
+ else if ($this->executedLines[$i] == -1) {
+ $color = 'lineNoCov';
+ $count = sprintf('%8d', 0);
+ }
+
+ // -2: Line is dead code.
+ else {
+ $color = 'lineDeadCode';
+ $count = ' ';
+ }
+
+ $css = sprintf(
+ '<span class="%s"> %s : ',
+
+ $color,
+ $count
+ );
+ }
+
+ $fillup = array_shift($this->codeLinesFillup);
+
+ if ($fillup > 0) {
+ $line .= str_repeat(' ', $fillup);
+ }
+
+ $lines .= sprintf(
+ '<span class="lineNum" id="container%d"><a name="%d"></a><a href="#%d" id="line%d">%8d</a> </span>%s%s%s' . "\n",
+
+ $i,
+ $i,
+ $i,
+ $i,
+ $i,
+ !empty($css) ? $css : ' : ',
+ !$this->highlight ? htmlspecialchars($line) : $line,
+ !empty($css) ? '</span>' : ''
+ );
+
+ $i++;
+ }
+
+ $items = '';
+
+ foreach ($this->classes as $className => $classData) {
+ $numCalledClasses = $classData['executedLines'] > 0 ? 1 : 0;
+ $calledClassesPercent = $numCalledClasses == 1 ? 100 : 0;
+
+ $numCalledMethods = 0;
+ $numMethods = count($classData['methods']);
+
+ foreach ($classData['methods'] as $method) {
+ if ($method['executedLines'] > 0) {
+ $numCalledMethods++;
+ }
+ }
+
+ $items .= $this->doRenderItem(
+ array(
+ 'name' => sprintf(
+ '<b><a href="#%d">%s</a></b>',
+
+ $classData['startLine'],
+ $className
+ ),
+ 'numClasses' => 1,
+ 'numCalledClasses' => $numCalledClasses,
+ 'calledClassesPercent' => sprintf('%01.2f', $calledClassesPercent),
+ 'numMethods' => $numMethods,
+ 'numCalledMethods' => $numCalledMethods,
+ 'calledMethodsPercent' => $this->calculatePercent(
+ $numCalledMethods, $numMethods
+ ),
+ 'numExecutableLines' => $classData['executableLines'],
+ 'numExecutedLines' => $classData['executedLines'],
+ 'executedLinesPercent' => $this->calculatePercent(
+ $classData['executedLines'], $classData['executableLines']
+ )
+ ),
+ $lowUpperBound,
+ $highLowerBound
+ );
+
+ foreach ($classData['methods'] as $methodName => $methodData) {
+ $numCalledMethods = $methodData['executedLines'] > 0 ? 1 : 0;
+ $calledMethodsPercent = $numCalledMethods == 1 ? 100 : 0;
+
+ $items .= $this->doRenderItem(
+ array(
+ 'name' => sprintf(
+ ' <a href="#%d">%s</a>',
+
+ $methodData['startLine'],
+ PHPUnit_Util_Class::getMethodSignature(
+ new ReflectionMethod($className, $methodName)
+ )
+ ),
+ 'numClasses' => '',
+ 'numCalledClasses' => '',
+ 'calledClassesPercent' => '',
+ 'numMethods' => 1,
+ 'numCalledMethods' => $numCalledMethods,
+ 'calledMethodsPercent' => sprintf('%01.2f', $calledMethodsPercent),
+ 'numExecutableLines' => $methodData['executableLines'],
+ 'numExecutedLines' => $methodData['executedLines'],
+ 'executedLinesPercent' => $this->calculatePercent(
+ $methodData['executedLines'], $methodData['executableLines']
+ )
+ ),
+ $lowUpperBound,
+ $highLowerBound,
+ 'method_item.html'
+ );
+ }
+ }
+
+ $this->setTemplateVars($template, $title, $charset);
+
+ $template->setVar(
+ array(
+ 'lines' => $lines,
+ 'total_item' => $this->renderTotalItem($lowUpperBound, $highLowerBound, FALSE),
+ 'items' => $items,
+ 'yuiPanelJS' => $this->yuiPanelJS
+ )
+ );
+
+ $cleanId = PHPUnit_Util_Filesystem::getSafeFilename($this->getId());
+ $template->renderTo($target . $cleanId . '.html');
+ }
+
+ /**
+ * Calculates coverage statistics for the file.
+ *
+ * @access protected
+ */
+ protected function calculateStatistics()
+ {
+ $classes = PHPUnit_Util_Class::getClassesInFile($this->getPath());
+
+ $startLines = array();
+ $endLines = array();
+
+ foreach ($classes as $class) {
+ if (!$class->isInterface()) {
+ $className = $class->getName();
+ $classStartLine = $class->getStartLine();
+ $classEndLine = $class->getEndLine();
+
+ $this->classes[$className] = array(
+ 'methods' => array(),
+ 'startLine' => $classStartLine,
+ 'executableLines' => 0,
+ 'executedLines' => 0
+ );
+
+ $startLines[$classStartLine] = &$this->classes[$className];
+ $endLines[$classEndLine] = &$this->classes[$className];
+
+ foreach ($class->getMethods() as $method) {
+ if (!$method->isAbstract() &&
+ $method->getDeclaringClass()->getName() == $className) {
+ $methodName = $method->getName();
+ $methodStartLine = $method->getStartLine();
+ $methodEndLine = $method->getEndLine();
+
+ $this->classes[$className]['methods'][$methodName] = array(
+ 'startLine' => $methodStartLine,
+ 'executableLines' => 0,
+ 'executedLines' => 0
+ );
+
+ $startLines[$methodStartLine] = &$this->classes[$className]['methods'][$methodName];
+ $endLines[$methodEndLine] = &$this->classes[$className]['methods'][$methodName];
+
+ $this->numMethods++;
+ }
+ }
+
+ $this->numClasses++;
+ }
+ }
+
+ $ignoreStart = -1;
+ $lineNumber = 1;
+
+ foreach ($this->codeLines as $line) {
+ if (isset($startLines[$lineNumber])) {
+ // Start line of a class.
+ if (isset($startLines[$lineNumber]['methods'])) {
+ $currentClass = &$startLines[$lineNumber];
+ }
+
+ // Start line of a method.
+ else {
+ $currentMethod = &$startLines[$lineNumber];
+ }
+ }
+
+ if (strpos($line, '@codeCoverageIgnore') !== FALSE) {
+ if (strpos($line, '@codeCoverageIgnoreStart') !== FALSE) {
+ $ignoreStart = $line;
+ }
+
+ else if (strpos($line, '@codeCoverageIgnoreEnd') !== FALSE) {
+ $ignoreStart = -1;
+ }
+ }
+
+ if (isset($this->executedLines[$lineNumber])) {
+ // Array: Line is executable and was executed.
+ if (is_array($this->executedLines[$lineNumber])) {
+ if (isset($currentClass)) {
+ $currentClass['executableLines']++;
+ $currentClass['executedLines']++;
+ }
+
+ if (isset($currentMethod)) {
+ $currentMethod['executableLines']++;
+ $currentMethod['executedLines']++;
+ }
+
+ $this->numExecutableLines++;
+ $this->numExecutedLines++;
+ }
+
+ // -1: Line is executable and was not executed.
+ else if ($this->executedLines[$lineNumber] == -1) {
+ if (isset($currentClass)) {
+ $currentClass['executableLines']++;
+ }
+
+ if (isset($currentMethod)) {
+ $currentMethod['executableLines']++;
+ }
+
+ $this->numExecutableLines++;
+
+ if ($ignoreStart != -1 && $line > $ignoreStart) {
+ if (isset($currentClass)) {
+ $currentClass['executedLines']++;
+ }
+
+ if (isset($currentMethod)) {
+ $currentMethod['executedLines']++;
+ }
+
+ $this->numExecutedLines++;
+ }
+ }
+ }
+
+ if (isset($endLines[$lineNumber])) {
+ // End line of a class.
+ if (isset($endLines[$lineNumber]['methods'])) {
+ unset($currentClass);
+ }
+
+ // End line of a method.
+ else {
+ unset($currentMethod);
+ }
+ }
+
+ $lineNumber++;
+ }
+
+ foreach ($this->classes as $class) {
+ foreach ($class['methods'] as $method) {
+ if ($method['executedLines'] > 0) {
+ $this->numCalledMethods++;
+ }
+ }
+
+ if ($class['executedLines'] > 0) {
+ $this->numCalledClasses++;
+ }
+ }
+ }
+
+ /**
+ * @author Aidan Lister <ai...@php.net>
+ * @author Sebastian Bergmann <sb...@sebastian-bergmann.de>
+ * @param string $file
+ * @return array
+ * @access protected
+ */
+ protected function loadFile($file)
+ {
+ $lines = explode("\n", str_replace("\t", ' ', file_get_contents($file)));
+ $result = array();
+
+ if (count($lines) == 0) {
+ return $result;
+ }
+
+ $lines = array_map('rtrim', $lines);
+ $linesLength = array_map('strlen', $lines);
+ $width = max($linesLength);
+
+ foreach ($linesLength as $line => $length) {
+ $this->codeLinesFillup[$line] = $width - $length;
+ }
+
+ if (!$this->highlight) {
+ return $lines;
+ }
+
+ $tokens = token_get_all(file_get_contents($file));
+ $stringFlag = FALSE;
+ $i = 0;
+ $result[$i] = '';
+
+ foreach ($tokens as $j => $token) {
+ if (is_string($token)) {
+ if ($token === '"' && $tokens[$j - 1] !== '\\') {
+ $result[$i] .= sprintf(
+ '<span class="string">%s</span>',
+
+ htmlspecialchars($token)
+ );
+
+ $stringFlag = !$stringFlag;
+ } else {
+ $result[$i] .= sprintf(
+ '<span class="keyword">%s</span>',
+
+ htmlspecialchars($token)
+ );
+ }
+
+ continue;
+ }
+
+ list ($token, $value) = $token;
+
+ $value = str_replace(
+ array("\t", ' '),
+ array(' ', ' '),
+ htmlspecialchars($value)
+ );
+
+ if ($value === "\n") {
+ $result[++$i] = '';
+ } else {
+ $lines = explode("\n", $value);
+
+ foreach ($lines as $jj => $line) {
+ $line = trim($line);
+
+ if ($line !== '') {
+ if ($stringFlag) {
+ $colour = 'string';
+ } else {
+ switch ($token) {
+ case T_INLINE_HTML: {
+ $colour = 'html';
+ }
+ break;
+
+ case T_COMMENT:
+ case T_DOC_COMMENT: {
+ $colour = 'comment';
+ }
+ break;
+
+ case T_ABSTRACT:
+ case T_ARRAY:
+ case T_ARRAY_CAST:
+ case T_AS:
+ case T_BOOLEAN_AND:
+ case T_BOOLEAN_OR:
+ case T_BOOL_CAST:
+ case T_BREAK:
+ case T_CASE:
+ case T_CATCH:
+ case T_CLASS:
+ case T_CLONE:
+ case T_CONCAT_EQUAL:
+ case T_CONTINUE:
+ case T_DEFAULT:
+ case T_DOUBLE_ARROW:
+ case T_DOUBLE_CAST:
+ case T_ECHO:
+ case T_ELSE:
+ case T_ELSEIF:
+ case T_EMPTY:
+ case T_ENDDECLARE:
+ case T_ENDFOR:
+ case T_ENDFOREACH:
+ case T_ENDIF:
+ case T_ENDSWITCH:
+ case T_ENDWHILE:
+ case T_END_HEREDOC:
+ case T_EXIT:
+ case T_EXTENDS:
+ case T_FINAL:
+ case T_FOREACH:
+ case T_FUNCTION:
+ case T_GLOBAL:
+ case T_IF:
+ case T_INC:
+ case T_INCLUDE:
+ case T_INCLUDE_ONCE:
+ case T_INSTANCEOF:
+ case T_INT_CAST:
+ case T_ISSET:
+ case T_IS_EQUAL:
+ case T_IS_IDENTICAL:
+ case T_IS_NOT_IDENTICAL:
+ case T_IS_SMALLER_OR_EQUAL:
+ case T_NEW:
+ case T_OBJECT_CAST:
+ case T_OBJECT_OPERATOR:
+ case T_PAAMAYIM_NEKUDOTAYIM:
+ case T_PRIVATE:
+ case T_PROTECTED:
+ case T_PUBLIC:
+ case T_REQUIRE:
+ case T_REQUIRE_ONCE:
+ case T_RETURN:
+ case T_SL:
+ case T_SL_EQUAL:
+ case T_SR:
+ case T_SR_EQUAL:
+ case T_START_HEREDOC:
+ case T_STATIC:
+ case T_STRING_CAST:
+ case T_THROW:
+ case T_TRY:
+ case T_UNSET_CAST:
+ case T_VAR:
+ case T_WHILE: {
+ $colour = 'keyword';
+ }
+ break;
+
+ default: {
+ $colour = 'default';
+ }
+ }
+ }
+
+ $result[$i] .= sprintf(
+ '<span class="%s">%s</span>',
+
+ $colour,
+ $line
+ );
+ }
+
+ if (isset($lines[$jj + 1])) {
+ $result[++$i] = '';
+ }
+ }
+ }
+ }
+
+ unset($result[count($result)-1]);
+
+ return $result;
+ }
+}
+?>