diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9d49909 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..01c98c6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# Path-based git attributes +# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html + +# Ignore all test and documentation with "export-ignore". +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/phpunit.xml.dist export-ignore +/.scrutinizer.yml export-ignore +/tests export-ignore \ No newline at end of file diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..e0425f1 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,23 @@ +filter: + excluded_paths: [tests/*] + +checks: + php: + remove_extra_empty_lines: true + remove_php_closing_tag: true + remove_trailing_whitespace: true + fix_use_statements: + remove_unused: true + preserve_multiple: false + preserve_blanklines: true + order_alphabetically: true + fix_php_opening_tag: true + fix_linefeed: true + fix_line_ending: true + fix_identation_4spaces: true + fix_doc_comments: true + +tools: + external_code_coverage: + timeout: 600 + runs: 3 \ No newline at end of file diff --git a/.styleci.yml b/.styleci.yml new file mode 100644 index 0000000..b46b01d --- /dev/null +++ b/.styleci.yml @@ -0,0 +1 @@ +preset: psr2 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5cbcff5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,27 @@ +language: php + +php: + - 5.5 + - 5.6 + - 7.0 + - hhvm + +# This triggers builds to run on the new TravisCI infrastructure. +# See: http://docs.travis-ci.com/user/workers/container-based-infrastructure/ +sudo: false + +matrix: + include: + - php: 5.5 + env: 'COMPOSER_FLAGS="--prefer-stable --prefer-lowest"' + +before_script: + - travis_retry composer self-update + - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source + +script: + - vendor/bin/phpcs --standard=psr2 src/ + - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover + +after_script: + - if [[ $TRAVIS_PHP_VERSION != 'hhvm' && $TRAVIS_PHP_VERSION != '7.0' ]]; then php vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover; fi \ No newline at end of file diff --git a/build/report.junit.xml b/build/report.junit.xml new file mode 100644 index 0000000..8e127ce --- /dev/null +++ b/build/report.junit.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/report.tap b/build/report.tap new file mode 100644 index 0000000..2b4e8b6 --- /dev/null +++ b/build/report.tap @@ -0,0 +1,3 @@ +TAP version 13 +ok 1 - Barryvdh\LaravelIdeHelper\ExampleTest::testCanInstantiate +1..1 diff --git a/composer.json b/composer.json index 0de08b9..034652f 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,9 @@ "symfony/class-loader": "~2.3|~3.0" }, "require-dev": { + "phpunit/phpunit" : "4.*", + "scrutinizer/ocular": "~1.1", + "squizlabs/php_codesniffer": "~2.3", "doctrine/dbal": "~2.3" }, "suggest": { @@ -28,6 +31,15 @@ "Barryvdh\\LaravelIdeHelper\\": "src" } }, + "autoload-dev": { + "psr-4": { + ":vendor\\:package_name\\": "tests" + } + }, + "scripts": { + "test": "phpunit", + "cs": "phpcs --standard=psr2 src/" + }, "extra": { "branch-alias": { "dev-master": "2.1-dev" diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..e6f4d37 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + tests + + + + + src/ + + + + + + + + + + \ No newline at end of file diff --git a/src/Alias.php b/src/Alias.php index 3996931..bcdbc95 100644 --- a/src/Alias.php +++ b/src/Alias.php @@ -1,284 +1,293 @@ - - * @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl) - * @license http://www.opensource.org/licenses/mit-license.php MIT - * @link https://github.com/barryvdh/laravel-ide-helper - */ - -namespace Barryvdh\LaravelIdeHelper; - -class Alias -{ - protected $alias; - protected $facade; - protected $extends = null; - protected $classType = 'class'; - protected $short; - protected $namespace = '__root'; - protected $root = null; - protected $classes = array(); - protected $methods = array(); - protected $usedMethods = array(); - protected $valid = false; - protected $magicMethods = array(); - protected $interfaces = array(); - - /** - * @param string $alias - * @param string $facade - * @param array $magicMethods - * @param array $interfaces - */ - public function __construct($alias, $facade, $magicMethods = array(), $interfaces = array()) - { - $this->alias = $alias; - $this->magicMethods = $magicMethods; - $this->interfaces = $interfaces; - - // Make the class absolute - $facade = '\\' . ltrim($facade, '\\'); - $this->facade = $facade; - - $this->detectRoot(); - - if ((!$this->isTrait() && $this->root)) { - $this->valid = true; - } else { - return; - } - - $this->addClass($this->root); - $this->detectNamespace(); - $this->detectClassType(); - - if($facade === '\Illuminate\Database\Eloquent\Model'){ - $this->usedMethods = array('decrement', 'increment'); - } - } - - /** - * Add one or more classes to analyze - * - * @param array|string $classes - */ - public function addClass($classes) - { - $classes = (array)$classes; - foreach ($classes as $class) { - if (class_exists($class) || interface_exists($class)) { - $this->classes[] = $class; - }else{ - echo "Class not exists: $class\r\n"; - } - } - } - - /** - * Check if this class is valid to process. - * @return bool - */ - public function isValid() - { - return $this->valid; - } - - /** - * Get the classtype, 'interface' or 'class' - * - * @return string - */ - public function getClasstype() - { - return $this->classType; - } - - /** - * Get the class which this alias extends - * - * @return null|string - */ - public function getExtends() - { - return $this->extends; - } - - /** - * Get the Alias by which this class is called - * - * @return string - */ - public function getAlias() - { - return $this->alias; - } - - /** - * Return the short name (without namespace) - */ - public function getShortName(){ - return $this->short; - } - /** - * Get the namespace from the alias - * - * @return string - */ - public function getNamespace() - { - return $this->namespace; - } - - /** - * Get the methods found by this Alias - * - * @return array - */ - public function getMethods() - { - $this->addMagicMethods(); - $this->detectMethods(); - return $this->methods; - } - - /** - * Detect the namespace - */ - protected function detectNamespace() - { - if (strpos($this->alias, '\\')) { - $nsParts = explode('\\', $this->alias); - $this->short = array_pop($nsParts); - $this->namespace = implode('\\', $nsParts); - }else{ - $this->short = $this->alias; - } - } - - /** - * Detect the class type - */ - protected function detectClassType() - { - //Some classes extend the facade - if (interface_exists($this->facade)) { - $this->classType = 'interface'; - $this->extends = $this->facade; - } else { - $this->classType = 'class'; - if (class_exists($this->facade)) { - $this->extends = $this->facade; - } - } - } - - /** - * Get the real root of a facade - * - * @return bool|string - */ - protected function detectRoot() - { - $facade = $this->facade; - - try { - //If possible, get the facade root - if (method_exists($facade, 'getFacadeRoot')) { - $root = get_class($facade::getFacadeRoot()); - } else { - $root = $facade; - } - - //If it doesn't exist, skip it - if (!class_exists($root) && !interface_exists($root)) { - return; - } - - $this->root = $root; - - //When the database connection is not set, some classes will be skipped - } catch (\PDOException $e) { - $this->error( - "PDOException: " . $e->getMessage() . "\nPlease configure your database connection correctly, or use the sqlite memory driver (-M). Skipping $facade." - ); - } catch (\Exception $e) { - $this->error("Exception: " . $e->getMessage() . "\nSkipping $facade."); - } - } - - /** - * Detect if this class is a trait or not. - * - * @return bool - */ - protected function isTrait() - { - // Check if the facade is not a Trait - if (function_exists('trait_exists') && trait_exists($this->facade)) { - return true; - } - return false; - } - - /** - * Add magic methods, as defined in the configuration files - */ - protected function addMagicMethods() - { - foreach($this->magicMethods as $magic => $real){ - list($className, $name) = explode('::', $real); - if(!class_exists($className) && !interface_exists($className)){ - continue; - } - $method = new \ReflectionMethod($className, $name); - $class = new \ReflectionClass($className); - - if(!in_array($method->name, $this->usedMethods)){ - if($class !== $this->root){ - $this->methods[] = new Method($method, $this->alias, $class, $magic, $this->interfaces); - } - $this->usedMethods[] = $magic; - } - } - } - - /** - * Get the methods for one or multiple classes. - * - * @return string - */ - protected function detectMethods() - { - - foreach ($this->classes as $class) { - $reflection = new \ReflectionClass($class); - - $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC); - if ($methods) { - foreach ($methods as $method) { - if (!in_array($method->name, $this->usedMethods)) { - // Only add the methods to the output when the root is not the same as the class. - // And don't add the __*() methods - if ($this->extends !== $class && substr($method->name, 0, 2) !== '__') { - $this->methods[] = new Method($method, $this->alias, $reflection, $method->name, $this->interfaces); - } - $this->usedMethods[] = $method->name; - } - } - } - } - } - - /** - * Output an error. - * - * @param string $string - * @return void - */ - protected function error($string) - { - echo $string . "\r\n"; - } -} + + * @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link https://github.com/barryvdh/laravel-ide-helper + */ + +namespace Barryvdh\LaravelIdeHelper; + +class Alias +{ + protected $alias; + protected $facade; + protected $extends = null; + protected $classType = 'class'; + protected $short; + protected $namespace = '__root'; + protected $root = null; + protected $classes = array(); + protected $methods = array(); + protected $usedMethods = array(); + protected $valid = false; + protected $magicMethods = array(); + protected $interfaces = array(); + + /** + * @param string $alias + * @param string $facade + * @param array $magicMethods + * @param array $interfaces + */ + public function __construct($alias, $facade, $magicMethods = array(), $interfaces = array()) + { + $this->alias = $alias; + $this->magicMethods = $magicMethods; + $this->interfaces = $interfaces; + + // Make the class absolute + $facade = '\\' . ltrim($facade, '\\'); + $this->facade = $facade; + + $this->detectRoot(); + + if ((!$this->isTrait() && $this->root)) { + $this->valid = true; + } else { + return; + } + + $this->addClass($this->root); + $this->detectNamespace(); + $this->detectClassType(); + + if ($facade === '\Illuminate\Database\Eloquent\Model') { + $this->usedMethods = array('decrement', 'increment'); + } + } + + /** + * Add one or more classes to analyze + * + * @param array|string $classes + */ + public function addClass($classes) + { + $classes = (array)$classes; + foreach ($classes as $class) { + if (class_exists($class) || interface_exists($class)) { + $this->classes[] = $class; + } else { + echo "Class not exists: $class\r\n"; + } + } + } + + /** + * Check if this class is valid to process. + * @return bool + */ + public function isValid() + { + return $this->valid; + } + + /** + * Get the classtype, 'interface' or 'class' + * + * @return string + */ + public function getClasstype() + { + return $this->classType; + } + + /** + * Get the class which this alias extends + * + * @return null|string + */ + public function getExtends() + { + return $this->extends; + } + + /** + * Get the Alias by which this class is called + * + * @return string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * Return the short name (without namespace) + */ + public function getShortName() + { + return $this->short; + } + /** + * Get the namespace from the alias + * + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * Get the methods found by this Alias + * + * @return array + */ + public function getMethods() + { + $this->addMagicMethods(); + $this->detectMethods(); + return $this->methods; + } + + /** + * Detect the namespace + */ + protected function detectNamespace() + { + if (strpos($this->alias, '\\')) { + $nsParts = explode('\\', $this->alias); + $this->short = array_pop($nsParts); + $this->namespace = implode('\\', $nsParts); + } else { + $this->short = $this->alias; + } + } + + /** + * Detect the class type + */ + protected function detectClassType() + { + //Some classes extend the facade + if (interface_exists($this->facade)) { + $this->classType = 'interface'; + $this->extends = $this->facade; + } else { + $this->classType = 'class'; + if (class_exists($this->facade)) { + $this->extends = $this->facade; + } + } + } + + /** + * Get the real root of a facade + * + * @return bool|string + */ + protected function detectRoot() + { + $facade = $this->facade; + + try { + //If possible, get the facade root + if (method_exists($facade, 'getFacadeRoot')) { + $root = get_class($facade::getFacadeRoot()); + } else { + $root = $facade; + } + + //If it doesn't exist, skip it + if (!class_exists($root) && !interface_exists($root)) { + return; + } + + $this->root = $root; + + //When the database connection is not set, some classes will be skipped + } catch (\PDOException $e) { + $this->error( + "PDOException: " . $e->getMessage() . + "\nPlease configure your database connection correctly, or use the sqlite memory driver (-M)." . + " Skipping $facade." + ); + } catch (\Exception $e) { + $this->error("Exception: " . $e->getMessage() . "\nSkipping $facade."); + } + } + + /** + * Detect if this class is a trait or not. + * + * @return bool + */ + protected function isTrait() + { + // Check if the facade is not a Trait + if (function_exists('trait_exists') && trait_exists($this->facade)) { + return true; + } + return false; + } + + /** + * Add magic methods, as defined in the configuration files + */ + protected function addMagicMethods() + { + foreach ($this->magicMethods as $magic => $real) { + list($className, $name) = explode('::', $real); + if (!class_exists($className) && !interface_exists($className)) { + continue; + } + $method = new \ReflectionMethod($className, $name); + $class = new \ReflectionClass($className); + + if (!in_array($method->name, $this->usedMethods)) { + if ($class !== $this->root) { + $this->methods[] = new Method($method, $this->alias, $class, $magic, $this->interfaces); + } + $this->usedMethods[] = $magic; + } + } + } + + /** + * Get the methods for one or multiple classes. + * + * @return string + */ + protected function detectMethods() + { + + foreach ($this->classes as $class) { + $reflection = new \ReflectionClass($class); + + $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC); + if ($methods) { + foreach ($methods as $method) { + if (!in_array($method->name, $this->usedMethods)) { + // Only add the methods to the output when the root is not the same as the class. + // And don't add the __*() methods + if ($this->extends !== $class && substr($method->name, 0, 2) !== '__') { + $this->methods[] = new Method( + $method, + $this->alias, + $reflection, + $method->name, + $this->interfaces + ); + } + $this->usedMethods[] = $method->name; + } + } + } + } + } + + /** + * Output an error. + * + * @param string $string + * @return void + */ + protected function error($string) + { + echo $string . "\r\n"; + } +} diff --git a/src/Console/GeneratorCommand.php b/src/Console/GeneratorCommand.php index 20112f8..1f41b43 100644 --- a/src/Console/GeneratorCommand.php +++ b/src/Console/GeneratorCommand.php @@ -17,7 +17,6 @@ use Illuminate\Filesystem\Filesystem; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; - /** * A command to generate autocomplete information for your IDE * @@ -60,7 +59,8 @@ class GeneratorCommand extends Command */ public function __construct( /*ConfigRepository */ $config, - Filesystem $files, /* Illuminate\View\Factory */ + Filesystem $files, + /* Illuminate\View\Factory */ $view ) { $this->config = $config; @@ -166,5 +166,4 @@ class GeneratorCommand extends Command array('sublime', "S", InputOption::VALUE_NONE, 'DEPRECATED: Use different style for SublimeText CodeIntel'), ); } - } diff --git a/src/Console/MetaCommand.php b/src/Console/MetaCommand.php index 1f6c439..bfad3d0 100644 --- a/src/Console/MetaCommand.php +++ b/src/Console/MetaCommand.php @@ -19,7 +19,8 @@ use Symfony\Component\Console\Output\OutputInterface; * * @author Barry vd. Heuvel */ -class MetaCommand extends Command { +class MetaCommand extends Command +{ /** * The console command name. @@ -54,7 +55,8 @@ class MetaCommand extends Command { * @param \Illuminate\Contracts\Filesystem\Filesystem $files * @param \Illuminate\Contracts\View\Factory $view */ - public function __construct($files, $view) { + public function __construct($files, $view) + { $this->files = $files; $this->view = $view; parent::__construct(); @@ -81,7 +83,7 @@ class MetaCommand extends Command { if (is_object($concrete)) { $bindings[$abstract] = get_class($concrete); } - }catch (\Exception $e) { + } catch (\Exception $e) { if ($this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { $this->comment("Cannot make '$abstract': ".$e->getMessage()); } @@ -105,7 +107,7 @@ class MetaCommand extends Command { /** * Get a list of abstracts from the Laravel Application. - * + * * @return array */ protected function getAbstracts() diff --git a/src/Console/ModelsCommand.php b/src/Console/ModelsCommand.php index 2be607b..5306d6b 100644 --- a/src/Console/ModelsCommand.php +++ b/src/Console/ModelsCommand.php @@ -75,8 +75,8 @@ class ModelsCommand extends Command $filename = $this->option('filename'); $this->write = $this->option('write'); $this->dirs = array_merge( - $this->laravel['config']->get('ide-helper.model_locations'), - $this->option('dir') + $this->laravel['config']->get('ide-helper.model_locations'), + $this->option('dir') ); $model = $this->argument('model'); $ignore = $this->option('ignore'); @@ -85,7 +85,7 @@ class ModelsCommand extends Command //If filename is default and Write is not specified, ask what to do if (!$this->write && $filename === $this->filename && !$this->option('nowrite')) { if ($this->confirm( - "Do you want to overwrite the existing model files? Choose no to write to $filename instead? (Yes/No): " + "Do you want to overwrite the existing model files? Choose no to write to $filename instead? (Yes/No): " ) ) { $this->write = true; @@ -201,12 +201,12 @@ class ModelsCommand extends Command $this->error("Exception: " . $e->getMessage() . "\nCould not analyze class $name."); } } - } if (!$hasDoctrine) { $this->error( - 'Warning: `"doctrine/dbal": "~2.3"` is required to load database information. Please require that in your composer.json and run `composer update`.' + 'Warning: `"doctrine/dbal": "~2.3"` is required to load database information. '. + 'Please require that in your composer.json and run `composer update`.' ); } @@ -291,7 +291,11 @@ class ModelsCommand extends Command $comment = $column->getComment(); $this->setProperty($name, $type, true, true, $comment); - $this->setMethod(Str::camel("where_" . $name), '\Illuminate\Database\Query\Builder|\\' . get_class($model), array('$value')); + $this->setMethod( + Str::camel("where_" . $name), + '\Illuminate\Database\Query\Builder|\\' . get_class($model), + array('$value') + ); } } } @@ -307,7 +311,7 @@ class ModelsCommand extends Command if (Str::startsWith($method, 'get') && Str::endsWith( $method, 'Attribute' - ) && $method !== 'getAttribute' + ) && $method !== 'getAttribute' ) { //Magic getAttribute $name = Str::snake(substr($method, 3, -9)); @@ -317,7 +321,7 @@ class ModelsCommand extends Command } elseif (Str::startsWith($method, 'set') && Str::endsWith( $method, 'Attribute' - ) && $method !== 'setAttribute' + ) && $method !== 'setAttribute' ) { //Magic setAttribute $name = Str::snake(substr($method, 3, -9)); @@ -334,8 +338,9 @@ class ModelsCommand extends Command array_shift($args); $this->setMethod($name, '\Illuminate\Database\Query\Builder|\\' . $reflection->class, $args); } - } elseif (!method_exists('Illuminate\Database\Eloquent\Model', $method) && !Str::startsWith($method, 'get')) { - + } elseif (!method_exists('Illuminate\Database\Eloquent\Model', $method) + && !Str::startsWith($method, 'get') + ) { //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php $reflection = new \ReflectionMethod($model, $method); @@ -364,24 +369,29 @@ class ModelsCommand extends Command ) as $relation) { $search = '$this->' . $relation . '('; if ($pos = stripos($code, $search)) { - //Resolve the relation's model to a Relation object. $relationObj = $model->$method(); if ($relationObj instanceof Relation) { $relatedModel = '\\' . get_class($relationObj->getRelated()); - if (in_array($relation, ['hasManyThrough', 'belongsToMany', 'hasMany', 'morphMany', 'morphToMany'])) { + $relations = ['hasManyThrough', 'belongsToMany', 'hasMany', 'morphMany', 'morphToMany']; + if (in_array($relation, $relations)) { //Collection or array of models (because Collection is Arrayable) $this->setProperty( - $method, - $this->getCollectionClass($relatedModel) . '|' . $relatedModel . '[]', - true, - null + $method, + $this->getCollectionClass($relatedModel) . '|' . $relatedModel . '[]', + true, + null ); } elseif ($relation === "morphTo") { // Model isn't specified because relation is polymorphic - $this->setProperty($method, '\Illuminate\Database\Eloquent\Model|\Eloquent', true, null); + $this->setProperty( + $method, + '\Illuminate\Database\Eloquent\Model|\Eloquent', + true, + null + ); } else { //Single model is returned $this->setProperty($method, $relatedModel, true, null); @@ -401,7 +411,7 @@ class ModelsCommand extends Command * @param bool|null $write * @param string|null $comment */ - protected function setProperty($name, $type = null, $read = null, $write = null, $comment='') + protected function setProperty($name, $type = null, $read = null, $write = null, $comment = '') { if (!isset($this->properties[$name])) { $this->properties[$name] = array(); diff --git a/src/Generator.php b/src/Generator.php index 686b3f5..c403e24 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -1,293 +1,302 @@ - - * @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl) - * @license http://www.opensource.org/licenses/mit-license.php MIT - * @link https://github.com/barryvdh/laravel-ide-helper - */ - -namespace Barryvdh\LaravelIdeHelper; - -use Illuminate\Foundation\AliasLoader; -use Illuminate\Config\Repository as ConfigRepository; -use ReflectionClass; -use Symfony\Component\Console\Output\OutputInterface; - -class Generator -{ - /** @var \Illuminate\Config\Repository */ - protected $config; - - /** @var \Illuminate\View\Factory */ - protected $view; - - /** @var \Symfony\Component\Console\Output\OutputInterface */ - protected $output; - - protected $extra = array(); - protected $magic = array(); - protected $interfaces = array(); - protected $helpers; - - /** - * @param \Illuminate\Config\Repository $config - * @param \Illuminate\View\Factory $view - * @param \Symfony\Component\Console\Output\OutputInterface $output - * @param string $helpers - */ - public function __construct(/*ConfigRepository */ $config, - /* Illuminate\View\Factory */ $view, - OutputInterface $output = null, - $helpers = '' - ) { - $this->config = $config; - $this->view = $view; - - // Find the drivers to add to the extra/interfaces - $this->detectDrivers(); - - $this->extra = array_merge($this->extra, $this->config->get('ide-helper.extra')); - $this->magic = array_merge($this->magic, $this->config->get('ide-helper.magic')); - $this->interfaces = array_merge($this->interfaces, $this->config->get('ide-helper.interfaces')); - // Make all interface classes absolute - foreach ($this->interfaces as &$interface) { - $interface = '\\' . ltrim($interface, '\\'); - } - $this->helpers = $helpers; - } - - /** - * Generate the helper file contents; - * - * @param string $format The format to generate the helper in (php/json) - * @return string; - */ - public function generate($format = 'php') - { - // Check if the generator for this format exists - $method = 'generate'.ucfirst($format).'Helper'; - if (method_exists($this, $method)) { - return $this->$method(); - } - - return $this->generatePhpHelper(); - } - - public function generatePhpHelper() - { - $app = app(); - return $this->view->make('ide-helper::helper') - ->with('namespaces', $this->getNamespaces()) - ->with('helpers', $this->helpers) - ->with('version', $app->version()) - ->render(); - } - - public function generateJsonHelper() - { - $classes = array(); - foreach ($this->getNamespaces() as $aliases) { - foreach($aliases as $alias) { - $functions = array(); - foreach ($alias->getMethods() as $method) { - $functions[$method->getName()] = '('. $method->getParamsWithDefault().')'; - } - $classes[$alias->getAlias()] = array( - 'functions' => $functions, - ); - } - } - - $flags = JSON_FORCE_OBJECT; - if (defined('JSON_PRETTY_PRINT')) { - $flags |= JSON_PRETTY_PRINT; - } - - return json_encode(array( - 'php' => array( - 'classes' => $classes, - ), - ), $flags); - } - - protected function detectDrivers() - { - $this->interfaces['\Illuminate\Contracts\Auth\Authenticatable'] = config('auth.providers.users.model', config('auth.model', 'App\User')); - - try{ - if (class_exists('Auth') && is_a('Auth', '\Illuminate\Support\Facades\Auth', true)) { - if (class_exists('\Illuminate\Foundation\Application')) { - $authMethod = version_compare(\Illuminate\Foundation\Application::VERSION, '5.2', '>=') ? 'guard' : 'driver'; - } else { - $refClass = new ReflectionClass('\Laravel\Lumen\Application'); - $versionStr = $refClass->newInstanceWithoutConstructor()->version(); - $authMethod = strpos($versionStr, 'Lumen (5.0') === 0 ? 'driver' : (strpos($versionStr, 'Lumen (5.1') === 0 ? 'driver' : 'guard'); - } - $class = get_class(\Auth::$authMethod()); - $this->extra['Auth'] = array($class); - $this->interfaces['\Illuminate\Auth\UserProviderInterface'] = $class; - } - }catch (\Exception $e) {} - - try{ - if (class_exists('DB') && is_a('DB', '\Illuminate\Support\Facades\DB', true)) { - $class = get_class(\DB::connection()); - $this->extra['DB'] = array($class); - $this->interfaces['\Illuminate\Database\ConnectionInterface'] = $class; - } - }catch (\Exception $e) {} - - try{ - if (class_exists('Cache') && is_a('Cache', '\Illuminate\Support\Facades\Cache', true)) { - $driver = get_class(\Cache::driver()); - $store = get_class(\Cache::getStore()); - $this->extra['Cache'] = array($driver, $store); - $this->interfaces['\Illuminate\Cache\StoreInterface'] = $store; - } - }catch (\Exception $e) {} - - try{ - if (class_exists('Queue') && is_a('Queue', '\Illuminate\Support\Facades\Queue', true)) { - $class = get_class(\Queue::connection()); - $this->extra['Queue'] = array($class); - $this->interfaces['\Illuminate\Queue\QueueInterface'] = $class; - } - }catch (\Exception $e) {} - - try{ - if (class_exists('SSH') && is_a('SSH', '\Illuminate\Support\Facades\SSH', true)){ - $class = get_class(\SSH::connection()); - $this->extra['SSH'] = array($class); - $this->interfaces['\Illuminate\Remote\ConnectionInterface'] = $class; - } - }catch (\Exception $e) {} - - try{ - if (class_exists('Storage') && is_a('Storage', '\Illuminate\Support\Facades\Storage', true)){ - $class = get_class(\Storage::disk()); - $this->extra['Storage'] = array($class); - $this->interfaces['\Illuminate\Contracts\Filesystem\Filesystem'] = $class; - } - }catch (\Exception $e) {} - - } - - /** - * Find all namespaces/aliases that are valid for us to render - * - * @return array - */ - protected function getNamespaces() - { - $namespaces = array(); - - // Get all aliases - foreach ($this->getAliases() as $name => $facade) { - // Skip the Redis facade, if not available (otherwise Fatal PHP Error) - if ($facade == 'Illuminate\Support\Facades\Redis' && !class_exists('Predis\Client')) { - continue; - } - - $magicMethods = array_key_exists($name, $this->magic) ? $this->magic[$name] : array(); - $alias = new Alias($name, $facade, $magicMethods, $this->interfaces); - if ($alias->isValid()) { - - //Add extra methods, from other classes (magic static calls) - if (array_key_exists($name, $this->extra)) { - $alias->addClass($this->extra[$name]); - } - - $namespace = $alias->getNamespace(); - if (!isset($namespaces[$namespace])) { - $namespaces[$namespace] = array(); - } - $namespaces[$namespace][] = $alias; - } - - } - - return $namespaces; - } - - protected function getAliases() - { - // For Laravel, use the AliasLoader - if (class_exists('Illuminate\Foundation\AliasLoader')) { - return AliasLoader::getInstance()->getAliases(); - } - - $facades = [ - 'App' => 'Illuminate\Support\Facades\App', - 'Auth' => 'Illuminate\Support\Facades\Auth', - 'Bus' => 'Illuminate\Support\Facades\Bus', - 'DB' => 'Illuminate\Support\Facades\DB', - 'Cache' => 'Illuminate\Support\Facades\Cache', - 'Cookie' => 'Illuminate\Support\Facades\Cookie', - 'Crypt' => 'Illuminate\Support\Facades\Crypt', - 'Event' => 'Illuminate\Support\Facades\Event', - 'Hash' => 'Illuminate\Support\Facades\Hash', - 'Log' => 'Illuminate\Support\Facades\Log', - 'Mail' => 'Illuminate\Support\Facades\Mail', - 'Queue' => 'Illuminate\Support\Facades\Queue', - 'Request' => 'Illuminate\Support\Facades\Request', - 'Schema' => 'Illuminate\Support\Facades\Schema', - 'Session' => 'Illuminate\Support\Facades\Session', - 'Storage' => 'Illuminate\Support\Facades\Storage', - //'Validator' => 'Illuminate\Support\Facades\Validator', - ]; - - $facades = array_merge($facades, $this->config->get('app.aliases', [])); - - // Only return the ones that actually exist - return array_filter($facades, function($alias){ - return class_exists($alias); - }); - } - - /** - * Get the driver/connection/store from the managers - * - * @param $alias - * @return array|bool|string - */ - public function getDriver($alias) - { - try { - if ($alias == "Auth") { - $driver = \Auth::driver(); - } elseif ($alias == "DB") { - $driver = \DB::connection(); - } elseif ($alias == "Cache") { - $driver = get_class(\Cache::driver()); - $store = get_class(\Cache::getStore()); - return array($driver, $store); - } elseif ($alias == "Queue") { - $driver = \Queue::connection(); - } else { - return false; - } - - return get_class($driver); - } catch (\Exception $e) { - $this->error("Could not determine driver/connection for $alias."); - return false; - } - } - - /** - * Write a string as error output. - * - * @param string $string - * @return void - */ - protected function error($string) - { - if($this->output){ - $this->output->writeln("$string"); - }else{ - echo $string . "\r\n"; - } - } -} + + * @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link https://github.com/barryvdh/laravel-ide-helper + */ + +namespace Barryvdh\LaravelIdeHelper; + +use Illuminate\Foundation\Application; +use Illuminate\Foundation\AliasLoader; +use Illuminate\Config\Repository as ConfigRepository; +use ReflectionClass; +use Symfony\Component\Console\Output\OutputInterface; + +class Generator +{ + /** @var \Illuminate\Config\Repository */ + protected $config; + + /** @var \Illuminate\View\Factory */ + protected $view; + + /** @var \Symfony\Component\Console\Output\OutputInterface */ + protected $output; + + protected $extra = array(); + protected $magic = array(); + protected $interfaces = array(); + protected $helpers; + + /** + * @param \Illuminate\Config\Repository $config + * @param \Illuminate\View\Factory $view + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @param string $helpers + */ + public function __construct( + /*ConfigRepository */ $config, + /* Illuminate\View\Factory */ $view, + OutputInterface $output = null, + $helpers = '' + ) { + $this->config = $config; + $this->view = $view; + + // Find the drivers to add to the extra/interfaces + $this->detectDrivers(); + + $this->extra = array_merge($this->extra, $this->config->get('ide-helper.extra')); + $this->magic = array_merge($this->magic, $this->config->get('ide-helper.magic')); + $this->interfaces = array_merge($this->interfaces, $this->config->get('ide-helper.interfaces')); + // Make all interface classes absolute + foreach ($this->interfaces as &$interface) { + $interface = '\\' . ltrim($interface, '\\'); + } + $this->helpers = $helpers; + } + + /** + * Generate the helper file contents; + * + * @param string $format The format to generate the helper in (php/json) + * @return string; + */ + public function generate($format = 'php') + { + // Check if the generator for this format exists + $method = 'generate'.ucfirst($format).'Helper'; + if (method_exists($this, $method)) { + return $this->$method(); + } + + return $this->generatePhpHelper(); + } + + public function generatePhpHelper() + { + $app = app(); + return $this->view->make('ide-helper::helper') + ->with('namespaces', $this->getNamespaces()) + ->with('helpers', $this->helpers) + ->with('version', $app->version()) + ->render(); + } + + public function generateJsonHelper() + { + $classes = array(); + foreach ($this->getNamespaces() as $aliases) { + foreach ($aliases as $alias) { + $functions = array(); + foreach ($alias->getMethods() as $method) { + $functions[$method->getName()] = '('. $method->getParamsWithDefault().')'; + } + $classes[$alias->getAlias()] = array( + 'functions' => $functions, + ); + } + } + + $flags = JSON_FORCE_OBJECT; + if (defined('JSON_PRETTY_PRINT')) { + $flags |= JSON_PRETTY_PRINT; + } + + return json_encode(array( + 'php' => array( + 'classes' => $classes, + ), + ), $flags); + } + + protected function detectDrivers() + { + $defaultUserModel = config('auth.providers.users.model', config('auth.model', 'App\User')); + $this->interfaces['\Illuminate\Contracts\Auth\Authenticatable'] = $defaultUserModel; + + try { + if (class_exists('Auth') && is_a('Auth', '\Illuminate\Support\Facades\Auth', true)) { + if (class_exists('\Illuminate\Foundation\Application')) { + $authMethod = version_compare(Application::VERSION, '5.2', '>=') ? 'guard' : 'driver'; + } else { + $refClass = new ReflectionClass('\Laravel\Lumen\Application'); + $versionStr = $refClass->newInstanceWithoutConstructor()->version(); + $authMethod = strpos($versionStr, 'Lumen (5.0') === 0 ? + 'driver' : + (strpos($versionStr, 'Lumen (5.1') === 0 ? 'driver' : 'guard'); + } + $class = get_class(\Auth::$authMethod()); + $this->extra['Auth'] = array($class); + $this->interfaces['\Illuminate\Auth\UserProviderInterface'] = $class; + } + } catch (\Exception $e) { + } + + try { + if (class_exists('DB') && is_a('DB', '\Illuminate\Support\Facades\DB', true)) { + $class = get_class(\DB::connection()); + $this->extra['DB'] = array($class); + $this->interfaces['\Illuminate\Database\ConnectionInterface'] = $class; + } + } catch (\Exception $e) { + } + + try { + if (class_exists('Cache') && is_a('Cache', '\Illuminate\Support\Facades\Cache', true)) { + $driver = get_class(\Cache::driver()); + $store = get_class(\Cache::getStore()); + $this->extra['Cache'] = array($driver, $store); + $this->interfaces['\Illuminate\Cache\StoreInterface'] = $store; + } + } catch (\Exception $e) { + } + + try { + if (class_exists('Queue') && is_a('Queue', '\Illuminate\Support\Facades\Queue', true)) { + $class = get_class(\Queue::connection()); + $this->extra['Queue'] = array($class); + $this->interfaces['\Illuminate\Queue\QueueInterface'] = $class; + } + } catch (\Exception $e) { + } + + try { + if (class_exists('SSH') && is_a('SSH', '\Illuminate\Support\Facades\SSH', true)) { + $class = get_class(\SSH::connection()); + $this->extra['SSH'] = array($class); + $this->interfaces['\Illuminate\Remote\ConnectionInterface'] = $class; + } + } catch (\Exception $e) { + } + + try { + if (class_exists('Storage') && is_a('Storage', '\Illuminate\Support\Facades\Storage', true)) { + $class = get_class(\Storage::disk()); + $this->extra['Storage'] = array($class); + $this->interfaces['\Illuminate\Contracts\Filesystem\Filesystem'] = $class; + } + } catch (\Exception $e) { + } + + } + + /** + * Find all namespaces/aliases that are valid for us to render + * + * @return array + */ + protected function getNamespaces() + { + $namespaces = array(); + + // Get all aliases + foreach ($this->getAliases() as $name => $facade) { + // Skip the Redis facade, if not available (otherwise Fatal PHP Error) + if ($facade == 'Illuminate\Support\Facades\Redis' && !class_exists('Predis\Client')) { + continue; + } + + $magicMethods = array_key_exists($name, $this->magic) ? $this->magic[$name] : array(); + $alias = new Alias($name, $facade, $magicMethods, $this->interfaces); + if ($alias->isValid()) { + //Add extra methods, from other classes (magic static calls) + if (array_key_exists($name, $this->extra)) { + $alias->addClass($this->extra[$name]); + } + + $namespace = $alias->getNamespace(); + if (!isset($namespaces[$namespace])) { + $namespaces[$namespace] = array(); + } + $namespaces[$namespace][] = $alias; + } + } + + return $namespaces; + } + + protected function getAliases() + { + // For Laravel, use the AliasLoader + if (class_exists('Illuminate\Foundation\AliasLoader')) { + return AliasLoader::getInstance()->getAliases(); + } + + $facades = [ + 'App' => 'Illuminate\Support\Facades\App', + 'Auth' => 'Illuminate\Support\Facades\Auth', + 'Bus' => 'Illuminate\Support\Facades\Bus', + 'DB' => 'Illuminate\Support\Facades\DB', + 'Cache' => 'Illuminate\Support\Facades\Cache', + 'Cookie' => 'Illuminate\Support\Facades\Cookie', + 'Crypt' => 'Illuminate\Support\Facades\Crypt', + 'Event' => 'Illuminate\Support\Facades\Event', + 'Hash' => 'Illuminate\Support\Facades\Hash', + 'Log' => 'Illuminate\Support\Facades\Log', + 'Mail' => 'Illuminate\Support\Facades\Mail', + 'Queue' => 'Illuminate\Support\Facades\Queue', + 'Request' => 'Illuminate\Support\Facades\Request', + 'Schema' => 'Illuminate\Support\Facades\Schema', + 'Session' => 'Illuminate\Support\Facades\Session', + 'Storage' => 'Illuminate\Support\Facades\Storage', + //'Validator' => 'Illuminate\Support\Facades\Validator', + ]; + + $facades = array_merge($facades, $this->config->get('app.aliases', [])); + + // Only return the ones that actually exist + return array_filter($facades, function ($alias) { + return class_exists($alias); + }); + } + + /** + * Get the driver/connection/store from the managers + * + * @param $alias + * @return array|bool|string + */ + public function getDriver($alias) + { + try { + if ($alias == "Auth") { + $driver = \Auth::driver(); + } elseif ($alias == "DB") { + $driver = \DB::connection(); + } elseif ($alias == "Cache") { + $driver = get_class(\Cache::driver()); + $store = get_class(\Cache::getStore()); + return array($driver, $store); + } elseif ($alias == "Queue") { + $driver = \Queue::connection(); + } else { + return false; + } + + return get_class($driver); + } catch (\Exception $e) { + $this->error("Could not determine driver/connection for $alias."); + return false; + } + } + + /** + * Write a string as error output. + * + * @param string $string + * @return void + */ + protected function error($string) + { + if ($this->output) { + $this->output->writeln("$string"); + } else { + echo $string . "\r\n"; + } + } +} diff --git a/src/IdeHelperServiceProvider.php b/src/IdeHelperServiceProvider.php index c88230b..d8285d6 100644 --- a/src/IdeHelperServiceProvider.php +++ b/src/IdeHelperServiceProvider.php @@ -67,9 +67,9 @@ class IdeHelperServiceProvider extends ServiceProvider ); $this->app['command.ide-helper.meta'] = $this->app->share( - function ($app) { - return new MetaCommand($app['files'], $app['view']); - } + function ($app) { + return new MetaCommand($app['files'], $app['view']); + } ); $this->commands('command.ide-helper.generate', 'command.ide-helper.models', 'command.ide-helper.meta'); @@ -84,5 +84,4 @@ class IdeHelperServiceProvider extends ServiceProvider { return array('command.ide-helper.generate', 'command.ide-helper.models'); } - } diff --git a/src/Method.php b/src/Method.php index c8c6715..d64eb60 100644 --- a/src/Method.php +++ b/src/Method.php @@ -1,313 +1,314 @@ - - * @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl) - * @license http://www.opensource.org/licenses/mit-license.php MIT - * @link https://github.com/barryvdh/laravel-ide-helper - */ - -namespace Barryvdh\LaravelIdeHelper; - -use phpDocumentor\Reflection\DocBlock; -use phpDocumentor\Reflection\DocBlock\Context; -use phpDocumentor\Reflection\DocBlock\Tag; -use phpDocumentor\Reflection\DocBlock\Tag\ReturnTag; -use phpDocumentor\Reflection\DocBlock\Tag\ParamTag; -use phpDocumentor\Reflection\DocBlock\Serializer as DocBlockSerializer; - -class Method -{ - /** @var \phpDocumentor\Reflection\DocBlock */ - protected $phpdoc; - - /** @var \ReflectionMethod */ - protected $method; - - protected $output = ''; - protected $name; - protected $namespace; - protected $params = array(); - protected $params_with_default = array(); - protected $interfaces = array(); - protected $return = null; - - /** - * @param \ReflectionMethod $method - * @param string $alias - * @param string $class - * @param string|null $methodName - * @param array $interfaces - */ - public function __construct(\ReflectionMethod $method, $alias, $class, $methodName = null, $interfaces = array()) - { - $this->method = $method; - $this->interfaces = $interfaces; - $this->name = $methodName ?: $method->name; - $this->namespace = $method->getDeclaringClass()->getNamespaceName(); - - //Create a DocBlock and serializer instance - $this->phpdoc = new DocBlock($method, new Context($this->namespace)); - - //Normalize the description and inherit the docs from parents/interfaces - try { - $this->normalizeParams($this->phpdoc); - $this->normalizeReturn($this->phpdoc); - $this->normalizeDescription($this->phpdoc); - } catch (\Exception $e) {} - - //Get the parameters, including formatted default values - $this->getParameters($method); - - //Make the method static - $this->phpdoc->appendTag(Tag::createInstance('@static', $this->phpdoc)); - - //Reference the 'real' function in the declaringclass - $declaringClass = $method->getDeclaringClass(); - $this->declaringClassName = '\\' . ltrim($declaringClass->name, '\\'); - $this->root = '\\' . ltrim($class->getName(), '\\'); - } - - /** - * Get the class wherein the function resides - * - * @return string - */ - public function getDeclaringClass() - { - return $this->declaringClassName; - } - - /** - * Return the class from which this function would be called - * - * @return string - */ - public function getRoot() - { - return $this->root; - } - - /** - * Get the docblock for this method - * - * @param string $prefix - * @return mixed - */ - public function getDocComment($prefix = "\t\t") - { - $serializer = new DocBlockSerializer(1, $prefix); - return $serializer->getDocComment($this->phpdoc); - } - - /** - * Get the method name - * - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * Get the parameters for this method - * - * @param bool $implode Wether to implode the array or not - * @return string - */ - public function getParams($implode = true) - { - return $implode ? implode(', ', $this->params) : $this->params; - } - - /** - * Get the parameters for this method including default values - * - * @param bool $implode Wether to implode the array or not - * @return string - */ - public function getParamsWithDefault($implode = true) - { - return $implode ? implode(', ', $this->params_with_default) : $this->params_with_default; - } - - /** - * Get the description and get the inherited docs. - * - * @param DocBlock $phpdoc - */ - protected function normalizeDescription(DocBlock $phpdoc) - { - //Get the short + long description from the DocBlock - $description = $phpdoc->getText(); - - //Loop through parents/interfaces, to fill in {@inheritdoc} - if (strpos($description, '{@inheritdoc}') !== false) { - $inheritdoc = $this->getInheritDoc($this->method); - $inheritDescription = $inheritdoc->getText(); - - $description = str_replace('{@inheritdoc}', $inheritDescription, $description); - $phpdoc->setText($description); - - $this->normalizeParams($inheritdoc); - $this->normalizeReturn($inheritdoc); - - //Add the tags that are inherited - $inheritTags = $inheritdoc->getTags(); - if ($inheritTags) { - /** @var Tag $tag */ - foreach ($inheritTags as $tag) { - $tag->setDocBlock(); - $phpdoc->appendTag($tag); - } - } - } - } - - /** - * Normalize the parameters - * - * @param DocBlock $phpdoc - */ - protected function normalizeParams(DocBlock $phpdoc) - { - //Get the return type and adjust them for beter autocomplete - $paramTags = $phpdoc->getTagsByName('param'); - if ($paramTags) { - /** @var ParamTag $tag */ - foreach($paramTags as $tag){ - // Convert the keywords - $content = $this->convertKeywords($tag->getContent()); - $tag->setContent($content); - - // Get the expanded type and re-set the content - $content = $tag->getType() . ' ' . $tag->getVariableName() . ' ' . $tag->getDescription(); - $tag->setContent(trim($content)); - } - } - } - - /** - * Normalize the return tag (make full namespace, replace interfaces) - * - * @param DocBlock $phpdoc - */ - protected function normalizeReturn(DocBlock $phpdoc) - { - //Get the return type and adjust them for beter autocomplete - $returnTags = $phpdoc->getTagsByName('return'); - if ($returnTags) { - /** @var ReturnTag $tag */ - $tag = reset($returnTags); - // Get the expanded type - $returnValue = $tag->getType(); - - // Replace the interfaces - foreach($this->interfaces as $interface => $real){ - $returnValue = str_replace($interface, $real, $returnValue); - } - - // Set the changed content - $tag->setContent($returnValue . ' ' . $tag->getDescription()); - $this->return = $returnValue; - }else{ - $this->return = null; - } - } - - /** - * Convert keywwords that are incorrect. - * - * @param string $string - * @return string - */ - protected function convertKeywords($string) - { - $string = str_replace('\Closure', 'Closure', $string); - $string = str_replace('Closure', '\Closure', $string); - $string = str_replace('dynamic', 'mixed', $string); - - return $string; - } - - /** - * Should the function return a value? - * - * @return bool - */ - public function shouldReturn() - { - if($this->return !== "void" && $this->method->name !== "__construct"){ - return true; - } - - return false; - } - - /** - * Get the parameters and format them correctly - * - * @param $method - * @return array - */ - public function getParameters($method) - { - //Loop through the default values for paremeters, and make the correct output string - $params = array(); - $paramsWithDefault = array(); - foreach ($method->getParameters() as $param) { - $paramStr = '$' . $param->getName(); - $params[] = $paramStr; - if ($param->isOptional()) { - $default = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null; - if (is_bool($default)) { - $default = $default ? 'true' : 'false'; - } elseif (is_array($default)) { - $default = 'array()'; - } elseif (is_null($default)) { - $default = 'null'; - } elseif (is_int($default)) { - //$default = $default; - } elseif (is_resource($default)) { - //skip to not fail - } else { - $default = "'" . trim($default) . "'"; - } - $paramStr .= " = $default"; - } - $paramsWithDefault[] = $paramStr; - } - - $this->params = $params; - $this->params_with_default = $paramsWithDefault; - } - - /** - * @param \ReflectionMethod $reflectionMethod - * @return DocBlock - */ - protected function getInheritDoc($reflectionMethod) - { - $parentClass = $reflectionMethod->getDeclaringClass()->getParentClass(); - - //Get either a parent or the interface - if ($parentClass) { - $method = $parentClass->getMethod($reflectionMethod->getName()); - } else { - $method = $reflectionMethod->getPrototype(); - } - if ($method) { - $namespace = $method->getDeclaringClass()->getNamespaceName(); - $phpdoc = new DocBlock($method, new Context($namespace)); - - if (strpos($phpdoc->getText(), '{@inheritdoc}') !== false) { - //Not at the end yet, try another parent/interface.. - return $this->getInheritDoc($method); - } else { - return $phpdoc; - } - } - } -} + + * @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link https://github.com/barryvdh/laravel-ide-helper + */ + +namespace Barryvdh\LaravelIdeHelper; + +use phpDocumentor\Reflection\DocBlock; +use phpDocumentor\Reflection\DocBlock\Context; +use phpDocumentor\Reflection\DocBlock\Tag; +use phpDocumentor\Reflection\DocBlock\Tag\ReturnTag; +use phpDocumentor\Reflection\DocBlock\Tag\ParamTag; +use phpDocumentor\Reflection\DocBlock\Serializer as DocBlockSerializer; + +class Method +{ + /** @var \phpDocumentor\Reflection\DocBlock */ + protected $phpdoc; + + /** @var \ReflectionMethod */ + protected $method; + + protected $output = ''; + protected $name; + protected $namespace; + protected $params = array(); + protected $params_with_default = array(); + protected $interfaces = array(); + protected $return = null; + + /** + * @param \ReflectionMethod $method + * @param string $alias + * @param \ReflectionClass $class + * @param string|null $methodName + * @param array $interfaces + */ + public function __construct(\ReflectionMethod $method, $alias, $class, $methodName = null, $interfaces = array()) + { + $this->method = $method; + $this->interfaces = $interfaces; + $this->name = $methodName ?: $method->name; + $this->namespace = $method->getDeclaringClass()->getNamespaceName(); + + //Create a DocBlock and serializer instance + $this->phpdoc = new DocBlock($method, new Context($this->namespace)); + + //Normalize the description and inherit the docs from parents/interfaces + try { + $this->normalizeParams($this->phpdoc); + $this->normalizeReturn($this->phpdoc); + $this->normalizeDescription($this->phpdoc); + } catch (\Exception $e) { + } + + //Get the parameters, including formatted default values + $this->getParameters($method); + + //Make the method static + $this->phpdoc->appendTag(Tag::createInstance('@static', $this->phpdoc)); + + //Reference the 'real' function in the declaringclass + $declaringClass = $method->getDeclaringClass(); + $this->declaringClassName = '\\' . ltrim($declaringClass->name, '\\'); + $this->root = '\\' . ltrim($class->getName(), '\\'); + } + + /** + * Get the class wherein the function resides + * + * @return string + */ + public function getDeclaringClass() + { + return $this->declaringClassName; + } + + /** + * Return the class from which this function would be called + * + * @return string + */ + public function getRoot() + { + return $this->root; + } + + /** + * Get the docblock for this method + * + * @param string $prefix + * @return mixed + */ + public function getDocComment($prefix = "\t\t") + { + $serializer = new DocBlockSerializer(1, $prefix); + return $serializer->getDocComment($this->phpdoc); + } + + /** + * Get the method name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get the parameters for this method + * + * @param bool $implode Wether to implode the array or not + * @return string + */ + public function getParams($implode = true) + { + return $implode ? implode(', ', $this->params) : $this->params; + } + + /** + * Get the parameters for this method including default values + * + * @param bool $implode Wether to implode the array or not + * @return string + */ + public function getParamsWithDefault($implode = true) + { + return $implode ? implode(', ', $this->params_with_default) : $this->params_with_default; + } + + /** + * Get the description and get the inherited docs. + * + * @param DocBlock $phpdoc + */ + protected function normalizeDescription(DocBlock $phpdoc) + { + //Get the short + long description from the DocBlock + $description = $phpdoc->getText(); + + //Loop through parents/interfaces, to fill in {@inheritdoc} + if (strpos($description, '{@inheritdoc}') !== false) { + $inheritdoc = $this->getInheritDoc($this->method); + $inheritDescription = $inheritdoc->getText(); + + $description = str_replace('{@inheritdoc}', $inheritDescription, $description); + $phpdoc->setText($description); + + $this->normalizeParams($inheritdoc); + $this->normalizeReturn($inheritdoc); + + //Add the tags that are inherited + $inheritTags = $inheritdoc->getTags(); + if ($inheritTags) { + /** @var Tag $tag */ + foreach ($inheritTags as $tag) { + $tag->setDocBlock(); + $phpdoc->appendTag($tag); + } + } + } + } + + /** + * Normalize the parameters + * + * @param DocBlock $phpdoc + */ + protected function normalizeParams(DocBlock $phpdoc) + { + //Get the return type and adjust them for beter autocomplete + $paramTags = $phpdoc->getTagsByName('param'); + if ($paramTags) { + /** @var ParamTag $tag */ + foreach ($paramTags as $tag) { + // Convert the keywords + $content = $this->convertKeywords($tag->getContent()); + $tag->setContent($content); + + // Get the expanded type and re-set the content + $content = $tag->getType() . ' ' . $tag->getVariableName() . ' ' . $tag->getDescription(); + $tag->setContent(trim($content)); + } + } + } + + /** + * Normalize the return tag (make full namespace, replace interfaces) + * + * @param DocBlock $phpdoc + */ + protected function normalizeReturn(DocBlock $phpdoc) + { + //Get the return type and adjust them for beter autocomplete + $returnTags = $phpdoc->getTagsByName('return'); + if ($returnTags) { + /** @var ReturnTag $tag */ + $tag = reset($returnTags); + // Get the expanded type + $returnValue = $tag->getType(); + + // Replace the interfaces + foreach ($this->interfaces as $interface => $real) { + $returnValue = str_replace($interface, $real, $returnValue); + } + + // Set the changed content + $tag->setContent($returnValue . ' ' . $tag->getDescription()); + $this->return = $returnValue; + } else { + $this->return = null; + } + } + + /** + * Convert keywwords that are incorrect. + * + * @param string $string + * @return string + */ + protected function convertKeywords($string) + { + $string = str_replace('\Closure', 'Closure', $string); + $string = str_replace('Closure', '\Closure', $string); + $string = str_replace('dynamic', 'mixed', $string); + + return $string; + } + + /** + * Should the function return a value? + * + * @return bool + */ + public function shouldReturn() + { + if ($this->return !== "void" && $this->method->name !== "__construct") { + return true; + } + + return false; + } + + /** + * Get the parameters and format them correctly + * + * @param $method + * @return array + */ + public function getParameters($method) + { + //Loop through the default values for paremeters, and make the correct output string + $params = array(); + $paramsWithDefault = array(); + foreach ($method->getParameters() as $param) { + $paramStr = '$' . $param->getName(); + $params[] = $paramStr; + if ($param->isOptional()) { + $default = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null; + if (is_bool($default)) { + $default = $default ? 'true' : 'false'; + } elseif (is_array($default)) { + $default = 'array()'; + } elseif (is_null($default)) { + $default = 'null'; + } elseif (is_int($default)) { + //$default = $default; + } elseif (is_resource($default)) { + //skip to not fail + } else { + $default = "'" . trim($default) . "'"; + } + $paramStr .= " = $default"; + } + $paramsWithDefault[] = $paramStr; + } + + $this->params = $params; + $this->params_with_default = $paramsWithDefault; + } + + /** + * @param \ReflectionMethod $reflectionMethod + * @return DocBlock + */ + protected function getInheritDoc($reflectionMethod) + { + $parentClass = $reflectionMethod->getDeclaringClass()->getParentClass(); + + //Get either a parent or the interface + if ($parentClass) { + $method = $parentClass->getMethod($reflectionMethod->getName()); + } else { + $method = $reflectionMethod->getPrototype(); + } + if ($method) { + $namespace = $method->getDeclaringClass()->getNamespaceName(); + $phpdoc = new DocBlock($method, new Context($namespace)); + + if (strpos($phpdoc->getText(), '{@inheritdoc}') !== false) { + //Not at the end yet, try another parent/interface.. + return $this->getInheritDoc($method); + } else { + return $phpdoc; + } + } + } +} diff --git a/tests/MethodTest.php b/tests/MethodTest.php new file mode 100644 index 0000000..69dba1b --- /dev/null +++ b/tests/MethodTest.php @@ -0,0 +1,19 @@ +assertInstanceOf(Method::class, $obj); + } +} \ No newline at end of file