This inspector provides a simple interface to the PHP Reflection API that can be used to gather information about any PHP source file for purposes of test metrics or static analysis.
Inheritance: extends lithium\core\StaticObject
Esempio n. 1
0
 /**
  * Analyzes code coverage results collected from XDebug, and performs coverage density analysis.
  *
  * @param object $report The report instance running this filter and aggregating results
  * @param array $classes A list of classes to analyze coverage on. By default, gets all
  *              defined subclasses of lithium\test\Unit which are currently in memory.
  * @return array|void Returns an array indexed by file and line, showing the number of
  *                    instances each line was called.
  */
 public static function analyze($report, array $classes = array())
 {
     $data = static::collect($report->results['filters'][__CLASS__]);
     $classes = $classes ?: array_filter(get_declared_classes(), function ($class) use($data) {
         $unit = 'lithium\\test\\Unit';
         return !is_subclass_of($class, $unit) || array_key_exists($class, $data);
     });
     $classes = array_values(array_intersect((array) $classes, array_keys($data)));
     $densities = $result = array();
     foreach ($classes as $class) {
         $classMap = array($class => Libraries::path($class));
         $densities += static::_density($data[$class], $classMap);
     }
     $executableLines = array();
     if (!empty($classes)) {
         $executableLines = array_combine($classes, array_map(function ($cls) {
             return Inspector::executable($cls, array('public' => false));
         }, $classes));
     }
     foreach ($densities as $class => $density) {
         $executable = $executableLines[$class];
         $covered = array_intersect(array_keys($density), $executable);
         $uncovered = array_diff($executable, $covered);
         $percentage = round(count($covered) / (count($executable) ?: 1), 4) * 100;
         $result[$class] = compact('class', 'executable', 'covered', 'uncovered', 'percentage');
     }
     $result = static::collectLines($result);
     return $result;
 }
Esempio n. 2
0
 /**
  * Creates step list for this exercise.
  * 
  * @return void
  */
 protected function _initSteps()
 {
     $methods = Inspector::methods($this)->to('array');
     foreach ($methods as $method) {
         if (substr($method['name'], 0, strlen($this->_methodPrefix)) === $this->_methodPrefix) {
             $this->_steps[] = $method['name'];
         }
     }
 }
Esempio n. 3
0
 /**
  * "Explain" methods step.
  *
  * @return void
  */
 public function explainExplainMethods()
 {
     $this->header("The Explain Methods");
     $this->out("The next step is to create your first `explain` method. When an exercise is run, each user-defined method that begins with 'explain' is run in the order it was defined.\n");
     $this->out("Let's start by defining a new method called `explainIntro`.");
     $this->halt();
     $methods = \lithium\analysis\Inspector::methods('\\li3_exercises\\extensions\\exercises\\Blog', 'extents');
     $this->assertTrue(isset($methods['explainIntro']), "Hmmm. I can't seem to find a method defined on your new Blog class named '{:purple}explainIntro{:end}'. {:error}Make sure it has public visibility!{:end}");
 }
Esempio n. 4
0
 /**
  * Get the methods to test.
  *
  * @param string $request
  * @return string
  */
 protected function _methods($request)
 {
     $use = $this->_use($request);
     $path = Libraries::path($use);
     if (!file_exists($path)) {
         return "";
     }
     $methods = (array) Inspector::methods($use, 'extents');
     $testMethods = array();
     foreach (array_keys($methods) as $method) {
         $testMethods[] = "\tpublic function test" . ucwords($method) . "() {}";
     }
     return join("\n", $testMethods);
 }
Esempio n. 5
0
 /**
  * Takes an instance of an object (usually a Collection object) containing test
  * instances. Introspects the test subject classes to extract cyclomatic complexity data.
  *
  * @param object $report Instance of Report which is calling apply.
  * @param array $tests The test to apply this filter on
  * @param array $options Not used.
  * @return object|void Returns the instance of `$tests`.
  */
 public static function apply($report, $tests, array $options = array())
 {
     $results = array();
     foreach ($tests->invoke('subject') as $class) {
         $results[$class] = array();
         if (!($methods = Inspector::methods($class, 'ranges', array('public' => false)))) {
             continue;
         }
         foreach ($methods as $method => $lines) {
             $lines = Inspector::lines($class, $lines);
             $branches = Parser::tokenize(join("\n", (array) $lines), array('include' => static::$_include));
             $results[$class][$method] = count($branches) + 1;
             $report->collect(__CLASS__, $results);
         }
     }
     return $tests;
 }
Esempio n. 6
0
 /**
  * Returns all classes directly depending on a given class.
  *
  * @param string $dependency The class name to use as a dependency.
  * @param string $exclude Regex path exclusion filter.
  * @return array Classes having a direct dependency on `$dependency`. May contain duplicates.
  */
 protected static function _affected($dependency, $exclude = null)
 {
     $exclude = $exclude ?: '/(tests|webroot|resources|libraries|plugins)/';
     $classes = Libraries::find(true, compact('exclude') + array('recursive' => true));
     $dependency = ltrim($dependency, '\\');
     $affected = array();
     foreach ($classes as $class) {
         if (isset(static::$_cachedDepends[$class])) {
             $depends = static::$_cachedDepends[$class];
         } else {
             $depends = Inspector::dependencies($class);
             $depends = array_map(function ($c) {
                 return ltrim($c, '\\');
             }, $depends);
             static::$_cachedDepends[$class] = $depends;
         }
         if (in_array($dependency, $depends)) {
             $affected[] = $class;
         }
     }
     return $affected;
 }
Esempio n. 7
0
 /**
  * Runs main console application logic. Returns a list of 
  * available exercises by default. Otherwise runs the specified exercise.
  *
  * @param string $command 
  * @return void
  */
 public function run($command = null)
 {
     if ($command == null) {
         $this->out("{:heading}EXERCISES{:end}");
         foreach ($this->_exercises as $key => $exercise) {
             $library = strtok($exercise, '\\');
             $info = Inspector::info($exercise);
             $this->out($this->_pad($key . " {:blue}via {$library}{:end}"), 'heading');
             $this->out($this->_pad($info['description'] == '' ? '(No description)' : $info['description']), 2);
         }
         $this->stop();
     }
     if (isset($this->_exercises[$command])) {
         $className = $this->_exercises[$command];
         $exercise = new $className(array('command' => $this));
         $exercise->run();
     } else {
         $this->out("{:error}The exercise {:end}{:blue}\"{$command}\"{:end}{:error} you specified cannot be found. Please supply a valid exercise name.{:end}");
         $this->out();
         $this->stop(1);
     }
 }
Esempio n. 8
0
 /**
  * Generate test cases in the given namespace.
  * `li3 create test model Post`
  * `li3 create test --library=li3_plugin model Post`
  *
  * @param string $type namespace of the class (e.g. model, controller, some.name.space).
  * @param string $name Name of class to test.
  * @return void
  */
 public function run($type = null, $name = null)
 {
     $library = Libraries::get($this->library);
     if (empty($library['prefix'])) {
         return false;
     }
     $namespace = $this->_namespace($type);
     $use = "\\{$library['prefix']}{$namespace}\\{$name}";
     $methods = array();
     if (class_exists($use)) {
         $methods = array();
         foreach (array_keys(Inspector::methods($use, 'extents')) as $method) {
             $methods[] = "\tpublic function test" . ucwords($method) . "() {}";
         }
     }
     $params = array('namespace' => "{$library['prefix']}tests\\cases\\{$namespace}", 'use' => $use, 'class' => "{$name}Test", 'methods' => join("\n", $methods));
     if ($this->_save($this->template, $params)) {
         $this->out("{$params['class']} created for {$name} in {$params['namespace']}.");
         return true;
     }
     return false;
 }
Esempio n. 9
0
 private function getMap($class)
 {
     $properties = Inspector::properties($class, array('public' => false));
     //parse out fields
     $fields = array();
     /** @var \ReflectionProperty $p */
     foreach ($properties as $p) {
         if (($p->getModifiers() & \ReflectionProperty::IS_PROTECTED) == \ReflectionProperty::IS_PROTECTED) {
             $name = $p->getName();
             if ($name[0] != '_') {
                 $fields[$name] = Docblock::comment($p->getDocComment());
             }
         }
     }
     //Parse out types
     $ret = array();
     foreach ($fields as $field => $data) {
         if (isset($data['tags']['var'])) {
             $ret[$field] = $this->dynamicType($data['tags']['var']);
         }
     }
     return $ret;
 }
Esempio n. 10
0
 /**
  * Custom check to determine if our given magic methods can be responded to.
  *
  * @param  string  $method     Method name.
  * @param  bool    $internal   Interal call or not.
  * @return bool
  */
 public function respondsTo($method, $internal = false)
 {
     $class = $this->_model;
     $modelRespondsTo = false;
     $parentRespondsTo = parent::respondsTo($method, $internal);
     $staticRespondsTo = $class::respondsTo($method, $internal);
     if (method_exists($class, '_object')) {
         $model = $class::invokeMethod('_object');
         $modelRespondsTo = $model->respondsTo($method);
     } else {
         $modelRespondsTo = Inspector::isCallable($class, $method, $internal);
     }
     return $parentRespondsTo || $staticRespondsTo || $modelRespondsTo;
 }
Esempio n. 11
0
 public function _parseClass($class)
 {
     $data = array();
     // $methods = Inspector::methods($class, null, array('public' => false));
     $methods = get_class_methods($class);
     $properties = array_keys(get_class_vars($class));
     $ident = $class;
     $info = Inspector::info($ident);
     $info = Docblock::comment($info['comment']);
     $data = $this->_merge($data, array('id' => $info['description'], 'comments' => array($ident)));
     $this->_merge($data, array('id' => $info['text'], 'comments' => array($class)));
     foreach ($methods as $method) {
         $ident = "{$class}::{$method}()";
         $info = Inspector::info($ident);
         $info = Docblock::comment($info['comment']);
         $this->_merge($data, array('id' => $info['description'], 'comments' => array($ident)));
         $this->_merge($data, array('id' => $info['text'], 'comments' => array($ident)));
         if (isset($info['tags']['return'])) {
             $this->_merge($data, array('id' => $info['tags']['return'], 'comments' => array($ident)));
         }
         foreach (Set::extract($info, '/tags/params/text') as $text) {
             $this->_merge($data, array('id' => $text, 'comments' => array($ident)));
         }
     }
     foreach ($properties as $property) {
         $ident = "{$class}::\${$property}";
         $info = Inspector::info($ident);
         $info = Docblock::comment($info['comment']);
         $data = $this->_merge($data, array('id' => $info['description'], 'comments' => array($ident)));
         $data = $this->_merge($data, array('id' => $info['text'], 'comments' => array($ident)));
     }
     return $data;
 }
Esempio n. 12
0
 /**
  * This docblock has an extra * in the closing element.
  *
  */
 public function testBadlyClosedDocblock()
 {
     $info = Inspector::info(__METHOD__ . '()');
     $description = 'This docblock has an extra * in the closing element.';
     $this->assertEqual($description, $info['description']);
     $this->assertEqual('', $info['text']);
 }
Esempio n. 13
0
 /**
  * Get the properties for the class
  *
  * @param string $class
  * @param array $options
  * @return array
  */
 protected function _properties($class, $options = array())
 {
     $defaults = array('name' => null);
     $options += $defaults;
     $properties = Inspector::properties($class);
     $results = array();
     foreach ($properties as &$property) {
         $comment = Docblock::comment($property['docComment']);
         $description = trim($comment['description']);
         $type = isset($comment['tags']['var']) ? strtok($comment['tags']['var'], ' ') : null;
         $name = str_replace('_', '-', Inflector::underscore($property['name']));
         $usage = $type == 'boolean' ? "-{$name}" : "--{$name}=" . strtoupper($name);
         $results[$name] = compact('name', 'description', 'type', 'usage');
         if ($name == $options['name']) {
             return array($name => $results[$name]);
         }
     }
     return $results;
 }
Esempio n. 14
0
 /**
  * Will determine if a method can be called.
  *
  * @param  string  $method     Method name.
  * @param  bool    $internal   Interal call or not.
  * @return bool
  */
 public static function respondsTo($method, $internal = false)
 {
     return Inspector::isCallable(get_called_class(), $method, $internal);
 }
Esempio n. 15
0
 /**
  * Convert an exception object to an exception result array for test reporting.
  *
  * @param object $exception The exception object to report on. Statistics are gathered and
  *               added to the reporting stack contained in `Unit::$_results`.
  * @param string $lineFlag
  * @return void
  * @todo Refactor so that reporters handle trace formatting.
  */
 protected function _reportException($exception, $lineFlag = null)
 {
     $initFrame = current($exception['trace']) + array('class' => '-', 'function' => '-');
     foreach ($exception['trace'] as $frame) {
         if (isset($scopedFrame)) {
             break;
         }
         if (!class_exists('lithium\\analysis\\Inspector')) {
             continue;
         }
         if (isset($frame['class']) && in_array($frame['class'], Inspector::parents($this))) {
             $scopedFrame = $frame;
         }
     }
     $trace = $exception['trace'];
     unset($exception['trace']);
     $this->_result('exception', $exception + array('class' => $initFrame['class'], 'method' => $initFrame['function'], 'trace' => Debugger::trace(array('trace' => $trace, 'format' => '{:functionRef}, line {:line}', 'includeScope' => false, 'scope' => array_filter(array('functionRef' => __NAMESPACE__ . '\\{closure}', 'line' => $lineFlag))))));
 }
Esempio n. 16
0
 /**
  * Helper method for caching closure function references to help the process of building the
  * stack trace.
  *
  * @param  array $frame Backtrace information.
  * @param  callable|string $function The method related to $frame information.
  * @return string Returns either the cached or the fetched closure function reference while
  *                writing its reference to the cache array `$_closureCache`.
  */
 protected static function _closureDef($frame, $function)
 {
     $reference = '::';
     $frame += array('file' => '??', 'line' => '??');
     $cacheKey = "{$frame['file']}@{$frame['line']}";
     if (isset(static::$_closureCache[$cacheKey])) {
         return static::$_closureCache[$cacheKey];
     }
     if ($class = Inspector::classes(array('file' => $frame['file']))) {
         foreach (Inspector::methods(key($class), 'extents') as $method => $extents) {
             $line = $frame['line'];
             if (!($extents[0] <= $line && $line <= $extents[1])) {
                 continue;
             }
             $class = key($class);
             $reference = "{$class}::{$method}";
             $function = "{$reference}()::{closure}";
             break;
         }
     } else {
         $reference = $frame['file'];
         $function = "{$reference}::{closure}";
     }
     $line = static::_definition($reference, $frame['line']) ?: '?';
     $function .= " @ {$line}";
     return static::$_closureCache[$cacheKey] = $function;
 }
Esempio n. 17
0
<h3 id="source">Source</h3>

<div id="sourceCode"></div>

<h3>Stack Trace</h3>

<div class="lithium-stack-trace">
    <ol>
        <?php 
foreach ($stack as $id => $frame) {
    ?>
            <?php 
    $location = "{$frame['file']}: {$frame['line']}";
    $lines = range($frame['line'] - $context, $frame['line'] + $context);
    $code = Inspector::lines($frame['file'], $lines);
    ?>
            <li>
                <tt><a href="#source" id="<?php 
    echo $id;
    ?>
" class="display-source-excerpt">
                    <?php 
    echo $frame['functionRef'];
    ?>
                </a></tt>
                <div id="sourceCode<?php 
    echo $id;
    ?>
" style="display: none;">
Esempio n. 18
0
 /**
  * Output the formatted available commands.
  *
  * @return void
  */
 protected function _renderCommands()
 {
     $commands = Libraries::locate('command', null, array('recursive' => false));
     foreach ($commands as $key => $command) {
         $library = strtok($command, '\\');
         if (!$key || strtok($commands[$key - 1], '\\') != $library) {
             $this->out("{:heading}COMMANDS{:end} {:blue}via {$library}{:end}");
         }
         $info = Inspector::info($command);
         $name = strtolower(Inflector::slug($info['shortName']));
         $this->out($this->_pad($name), 'heading');
         $this->out($this->_pad($info['description']), 2);
     }
     $message = 'See `{:command}li3 help COMMAND{:end}`';
     $message .= ' for more information on a specific command.';
     $this->out($message, 2);
 }
Esempio n. 19
0
 /**
  * This is a helper method for testStorageMechanism to fetch a private
  * property of the Inflector class.
  *
  * @param string $property
  * @return string The value of the property.
  */
 private function getProtectedValue($property)
 {
     $info = Inspector::info("lithium\\util\\Inflector::{$property}");
     return $info['value'];
 }
Esempio n. 20
0
 /**
  * Determines if a given method can be called.
  *
  * @param string $method Name of the method.
  * @param boolean $internal Provide `true` to perform check from inside the
  *                class/object. When `false` checks also for public visibility;
  *                defaults to `false`.
  * @return boolean Returns `true` if the method can be called, `false` otherwise.
  */
 public function respondsTo($method, $internal = false)
 {
     if (method_exists($class = $this->_model, '_object')) {
         $result = $class::invokeMethod('_object')->respondsTo($method);
     } else {
         $result = Inspector::isCallable($class, $method, $internal);
     }
     $result = $result || parent::respondsTo($method, $internal);
     $result = $result || $class::respondsTo($method, $internal);
     return $result;
 }
Esempio n. 21
0
 /**
  * Tests getting static and non-static properties from various types of classes.
  *
  * @return void
  */
 public function testGetClassProperties()
 {
     $result = array_map(function ($property) {
         return $property['name'];
     }, Inspector::properties(__CLASS__));
     $expected = array('test', 'test2');
     $this->assertEqual($expected, $result);
     $result = array_map(function ($property) {
         return $property['name'];
     }, Inspector::properties(__CLASS__, array('public' => false)));
     $expected = array('test', 'test2', '_test');
     $this->assertEqual($expected, $result);
     $result = Inspector::properties(__CLASS__);
     $expected = array(array('modifiers' => array('public'), 'docComment' => false, 'name' => 'test', 'value' => null), array('modifiers' => array('public', 'static'), 'docComment' => false, 'name' => 'test2', 'value' => 'bar'));
     $this->assertEqual($expected, $result);
     $result = array_map(function ($property) {
         return $property['name'];
     }, Inspector::properties('lithium\\action\\Controller'));
     $this->assertTrue(in_array('request', $result));
     $this->assertTrue(in_array('response', $result));
     $this->assertFalse(in_array('_render', $result));
     $this->assertFalse(in_array('_classes', $result));
     $result = array_map(function ($property) {
         return $property['name'];
     }, Inspector::properties('lithium\\action\\Controller', array('public' => false)));
     $this->assertTrue(in_array('request', $result));
     $this->assertTrue(in_array('response', $result));
     $this->assertTrue(in_array('_render', $result));
     $this->assertTrue(in_array('_classes', $result));
     $this->assertNull(Inspector::properties('\\lithium\\core\\Foo'));
 }
Esempio n. 22
0
 protected static function _extractFileCode($ref, $options)
 {
     $library = Libraries::get($options['library']);
     $file = $ref['file'];
     if (!($path = realpath("{$library['path']}/{$file}"))) {
         return;
     }
     list($start, $end) = array_map('intval', explode('-', $ref['lines']));
     $lines = Inspector::lines(file_get_contents($path), range($start, $end));
     return array("{$ref['file']}::{$ref['lines']}", "\t" . join("\n\t", $lines));
 }
Esempio n. 23
0
 protected static function _class(array $object, array $data, array $options = array())
 {
     $identifier = $object['identifier'];
     $proto = array('parent' => get_parent_class($identifier), 'methods' => Inspector::methods($identifier, null, array('public' => false)), 'properties' => Inspector::properties($identifier, array('public' => false)));
     $classes = Libraries::find($object['library'], array('recursive' => true));
     $proto['subClasses'] = array_filter($classes, function ($class) use($identifier) {
         if (preg_match('/\\\\(libraries)\\\\|\\\\(mocks)\\\\|\\\\(tests)\\\\/', $class)) {
             return false;
         }
         try {
             return get_parent_class($class) == $identifier;
         } catch (Exception $e) {
             return false;
         }
     });
     sort($proto['subClasses']);
     return $proto + $object + array('tags' => isset($data['tags']) ? $data['tags'] : array());
 }
Esempio n. 24
0
 public function lines($data, $start, $end)
 {
     return Inspector::lines($data, range($start, $end));
 }
Esempio n. 25
0
 public function testCallableVisibility()
 {
     $obj = new MockMethodFiltering();
     $this->assertTrue(Inspector::isCallable($obj, 'method', 0));
     $this->assertTrue(Inspector::isCallable($obj, 'method', 1));
     $this->assertFalse(Inspector::isCallable('lithium\\action\\Dispatcher', '_callable', 0));
     $this->assertTrue(Inspector::isCallable('lithium\\action\\Dispatcher', '_callable', 1));
 }
Esempio n. 26
0
 /**
  * Will determine if a method can be called.
  *
  * @param  string  $method     Method name.
  * @param  bool    $internal   Interal call or not.
  * @return bool
  */
 public function respondsTo($method, $internal = false)
 {
     return Inspector::isCallable($this, $method, $internal);
 }
Esempio n. 27
0
 /**
  * Convert an exception object to an exception result array for test reporting.
  *
  * @param array $exception The exception data to report on. Statistics are gathered and
  *               added to the reporting stack contained in `Unit::$_results`.
  * @param string $lineFlag
  * @return void
  * @todo Refactor so that reporters handle trace formatting.
  */
 protected function _reportException($exception, $lineFlag = null)
 {
     $message = $exception['message'];
     $isExpected = ($exp = end($this->_expected)) && ($exp === true || $exp === $message || Validator::isRegex($exp) && preg_match($exp, $message));
     if ($isExpected) {
         return array_pop($this->_expected);
     }
     $initFrame = current($exception['trace']) + array('class' => '-', 'function' => '-');
     foreach ($exception['trace'] as $frame) {
         if (isset($scopedFrame)) {
             break;
         }
         if (!class_exists('lithium\\analysis\\Inspector')) {
             continue;
         }
         if (isset($frame['class']) && in_array($frame['class'], Inspector::parents($this))) {
             $scopedFrame = $frame;
         }
     }
     if (class_exists('lithium\\analysis\\Debugger')) {
         $exception['trace'] = Debugger::trace(array('trace' => $exception['trace'], 'format' => '{:functionRef}, line {:line}', 'includeScope' => false, 'scope' => array_filter(array('functionRef' => __NAMESPACE__ . '\\{closure}', 'line' => $lineFlag))));
     }
     $this->_result('exception', $exception + array('class' => $initFrame['class'], 'method' => $initFrame['function']));
 }
Esempio n. 28
0
 /**
  * Tests getting static and non-static properties from various types of classes.
  *
  * @return void
  */
 public function testGetClassProperties()
 {
     $result = array_map(function ($property) {
         return $property['name'];
     }, Inspector::properties(__CLASS__));
     $expected = array('test', 'test2');
     $this->assertEqual($expected, $result);
     $result = array_map(function ($property) {
         return $property['name'];
     }, Inspector::properties(__CLASS__, array('public' => false)));
     $expected = array('test', 'test2', '_test');
     $this->assertEqual($expected, $result);
     $result = Inspector::properties(__CLASS__);
     $expected = array(array('modifiers' => array('public'), 'docComment' => false, 'name' => 'test', 'value' => null), array('modifiers' => array('public', 'static'), 'docComment' => false, 'name' => 'test2', 'value' => 'bar'));
     $this->assertEqual($expected, $result);
 }