From 8193fa0763fda0bbc633fe569952a1ff4c77382a Mon Sep 17 00:00:00 2001 From: "Barry vd. Heuvel" Date: Wed, 6 Aug 2014 00:02:00 +0200 Subject: [PATCH] Big refactor --- src/Alias.php | 241 +++++++++++++++ src/Console/GeneratorCommand.php | 507 +++++-------------------------- src/Console/ModelsCommand.php | 239 +++++++++------ src/Generator.php | 130 ++++++++ src/IdeHelperServiceProvider.php | 84 ++--- src/Method.php | 246 +++++++++++++++ src/config/config.php | 0 src/views/ide-helper.php | 36 +++ 8 files changed, 913 insertions(+), 570 deletions(-) create mode 100644 src/Alias.php create mode 100644 src/Generator.php create mode 100644 src/Method.php mode change 100755 => 100644 src/config/config.php create mode 100644 src/views/ide-helper.php diff --git a/src/Alias.php b/src/Alias.php new file mode 100644 index 0000000..e5d989c --- /dev/null +++ b/src/Alias.php @@ -0,0 +1,241 @@ + + * @copyright 2013 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 $namespace = '__root'; + protected $root = null; + protected $classes = array(); + protected $methods = array(); + protected $used_methods = array(); + protected $valid = false; + + public function __construct($alias, $facade) + { + $this->alias = $alias; + + // 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(); + + + } + + /** + * 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; + } + } + + } + + /** + * 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; + } + + /** + * 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->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); + } + } + + /** + * 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)) { + $this->error("Class $this->root is not found."); + 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; + } + + /** + * 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->used_methods)) { + // Only add the methods to the output when the root is not the same as the facade. + // And don't add the __*() methods + if ($this->facade !== $this->root && substr($method->name, 0, 2) !== '__') { + $this->methods[] = new Method($method, $this->alias, $reflection); + } + $this->used_methods[] = $method->name; + } + } + } + } + } + + // Helper class to log errors + protected function error($msg) + { + echo $msg . "\r\n"; + } + +} diff --git a/src/Console/GeneratorCommand.php b/src/Console/GeneratorCommand.php index eb75cab..50812e8 100644 --- a/src/Console/GeneratorCommand.php +++ b/src/Console/GeneratorCommand.php @@ -9,7 +9,11 @@ */ namespace Barryvdh\LaravelIdeHelper\Console; + +use Barryvdh\LaravelIdeHelper\Generator; +use Illuminate\Config\Repository as ConfigRepository; use Illuminate\Console\Command; +use Illuminate\Filesystem\Filesystem; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Illuminate\Foundation\AliasLoader; @@ -17,12 +21,14 @@ use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock\Context; use phpDocumentor\Reflection\DocBlock\Tag; use phpDocumentor\Reflection\DocBlock\Serializer as DocBlockSerializer; + /** * A command to generate autocomplete information for your IDE * * @author Barry vd. Heuvel */ -class GeneratorCommand extends Command { +class GeneratorCommand extends Command +{ /** * The console command name. @@ -38,12 +44,38 @@ class GeneratorCommand extends Command { */ protected $description = 'Generate a new IDE Helper file.'; + /** @var \Illuminate\Config\Repository */ + protected $config; + + /** @var \Illuminate\Filesystem\Filesystem */ + protected $files; + + /** @var \Illuminate\View\Factory */ + protected $view; + protected $extra; protected $onlyExtend; protected $helpers; protected $magic; + /** + * + * @param \Illuminate\Config\Repository $config + * @param \Illuminate\Filesystem\Filesystem $files + * @param \Illuminate\View\Factory $view + */ + public function __construct( + ConfigRepository $config, + Filesystem $files, /* Illuminate\View\Factory */ + $view + ) { + $this->config = $config; + $this->files = $files; + $this->view = $view; + parent::__construct(); + } + /** * Execute the console command. * @@ -51,43 +83,55 @@ class GeneratorCommand extends Command { */ public function fire() { - if (file_exists($compiled = base_path().'/bootstrap/compiled.php')){ - $this->error('Error generating IDE Helper: first delete bootstrap/compiled.php (php artisan clear-compiled)'); - }else{ + if (file_exists($compiled = base_path() . '/bootstrap/compiled.php')) { + $this->error( + 'Error generating IDE Helper: first delete bootstrap/compiled.php (php artisan clear-compiled)' + ); + } else { $filename = $this->argument('filename'); - - if($this->option('memory')){ + + if ($this->option('memory')) { $this->useMemoryDriver(); } - - $this->extra = \Config::get('laravel-ide-helper::extra'); - $this->magic = \Config::get('laravel-ide-helper::magic'); - if( $this->option('helpers') || (\Config::get('laravel-ide-helper::include_helpers') )){ - $this->helpers = \Config::get('laravel-ide-helper::helper_files'); - }else{ - $this->helpers = array(); + $extra = $this->config->get('laravel-ide-helper::extra'); + $magic = $this->config->get('laravel-ide-helper::magic'); + $helpers = ''; + if ($this->option('helpers') || ($this->config->get('laravel-ide-helper::include_helpers'))) { + foreach ($this->config->get('laravel-ide-helper::helper_files', array()) as $helper) { + if (file_exists($helper)) { + $helpers .= str_replace(array(''), '', $this->files->get($helper)); + } + } + } else { + $helpers = ''; } - $content = $this->generateDocs(); - - $written = \File::put($filename, $content); - - if($written !== false){ + $generator = new Generator($this->view, $extra, $magic, $helpers); + $output = $generator->generate(); + + + $written = $this->files->put($filename, $output); + + if ($written !== false) { $this->info("A new helper file was written to $filename"); - }else{ + } else { $this->error("The helper file could not be created at $filename"); } - } + } } - protected function useMemoryDriver(){ + protected function useMemoryDriver() + { //Use a sqlite database in memory, to avoid connection errors on Database facades - \Config::set('database.connections.sqlite',array( - 'driver' => 'sqlite', + $this->config->set( + 'database.connections.sqlite', + array( + 'driver' => 'sqlite', 'database' => ':memory:', - )); - \Config::set('database.default', 'sqlite'); + ) + ); + $this->config->set('database.default', 'sqlite'); } /** @@ -98,7 +142,12 @@ class GeneratorCommand extends Command { protected function getArguments() { return array( - array('filename', InputArgument::OPTIONAL, 'The path to the helper file', \Config::get('laravel-ide-helper::filename')), + array( + 'filename', + InputArgument::OPTIONAL, + 'The path to the helper file', + $this->config->get('laravel-ide-helper::filename') + ), ); } @@ -116,410 +165,4 @@ class GeneratorCommand extends Command { ); } - /** - * Generate the docs for all facades in the AliasLoader - * - * @return string - */ - protected function generateDocs(){ - - $aliasLoader = AliasLoader::getInstance(); - - $outputString = " - */\n\n"; - $output['__root'] = "\texit('Only to be used as an helper for your IDE');\n\n"; - - //Get all aliases - $aliases = $aliasLoader->getAliases(); - - foreach($aliases as $alias => $facade){ - //Check if the facade is not a Trait - if(function_exists('trait_exists') && trait_exists($facade)){ - continue; - } - $facade = '\\'.ltrim($facade, '\\'); - $root = $this->getRoot($facade); - if(!$root){ - continue; - } - - try{ - if (strpos($alias, '\\')){ - $nsParts = explode('\\', $alias); - $alias = array_pop($nsParts); - $ns = implode('\\', $nsParts); - } else { - $ns = '__root'; - } - if (!isset($output[$ns])) $output[$ns] = ''; - - //Some classes extend the facade - if(interface_exists($facade)){ - $output[$ns] .= "\tinterface $alias extends $facade{\n"; - }elseif(class_exists($facade)){ - $output[$ns] .= "\tclass $alias extends $facade{\n"; - }else{ - $output[$ns] .= "\tclass $alias{\n"; - } - - $usedMethods = array(); - - //Only add the methods to the output when the root is not the same as the facade. - $addOutput = ($root !== $facade); - $output[$ns] .= $this->getMethods($root, $alias, $usedMethods, $addOutput); - - - $driver = $this->getDriver($alias); - if($driver){ - $output[$ns] .= $this->getMethods($driver, $alias, $usedMethods); - } - - //Add extra methods, from other classes (magic static calls) - if(array_key_exists($alias, $this->extra)){ - $output[$ns] .= $this->getMethods($this->extra[$alias], $alias, $usedMethods); - } - - //Add extra methods, from other classes (magic static calls) - if(array_key_exists($alias, $this->magic)){ - $output[$ns] .= $this->addMagicMethods($this->magic[$alias], $alias, $usedMethods); - } - - - $output[$ns] .= "\t}\n"; - - }catch(\Exception $e){ - $this->error("Exception: ".$e->getMessage()."\nCould not analyze $root."); - } - - } - - //Include the helper file, if requested - if(!empty($this->helpers)){ - foreach($this->helpers as $helper){ - if (file_exists($helper)){ - $output['__root'] .= str_replace(array(''), '', \File::get($helper)); - } - } - } - - foreach ($output as $ns => $body){ - if ($ns == '__root'){ - $ns = ''; - } - $outputString .= "namespace $ns{\n"; - $outputString .= $body; - $outputString .= "}\n\n"; - } - - return $outputString; - } - - /** - * Get the real root of a facade - * - * @param $facade - * @return bool|string - */ - protected function getRoot($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)){ - $this->error("Class $root is not found."); - return false; - } - - return $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."); - return false; - }catch(\Exception $e){ - $this->error("Exception: ".$e->getMessage()."\nSkipping $facade."); - return false; - } - - } - - /** - * 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; - } - } - - /** - * Get the methods for one or multiple classes. - * - * @param $classes - * @param $alias - * @param $usedMethods - * @param bool $addOutput - * @return string - */ - protected function getMethods($classes, $alias, &$usedMethods, $addOutput = true){ - if(!is_array($classes)){ - $classes = array($classes); - } - $output = ''; - foreach($classes as $class){ - if(!class_exists($class) && !interface_exists($class)){ - continue; - } - $reflection = new \ReflectionClass($class); - - $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC); - if($methods) - { - foreach ($methods as $method) - { - if(!in_array($method->name, $usedMethods)){ - if( $addOutput){ - $output .= $this->parseMethod($method, $alias, $reflection); - } - $usedMethods[] = $method->name; - } - } - } - } - return $output; - } - - /** - * Get the methods for one or multiple magic methods. - * - * @param $methods - * @param $alias - * @param $usedMethods - * @param bool $addOutput - * @return string - */ - protected function addMagicMethods($methods, $alias, &$usedMethods, $addOutput = true){ - $output = ''; - foreach($methods 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, $usedMethods)){ - if( $addOutput){ - $output .= $this->parseMethod($method, $alias, $class, $magic); - } - $usedMethods[] = $method->name; - } - - - $usedMethods[] = $magic; - - } - return $output; - } - - /** - * @param \ReflectionMethod $method - * @param string $alias - * @param $class - * @param null $methodName - * @return string - */ - protected function parseMethod($method, $alias, $class, $methodName = null){ - $output = ''; - - // Don't add the __clone() functions - if($method->name === '__clone'){ - return $output; - } - $methodName = $methodName ?: $method->name; - - $namespace = $method->getDeclaringClass()->getNamespaceName(); - - //Create a DocBlock and serializer instance - $phpdoc = new DocBlock($method, new Context($namespace)); - $serializer = new DocBlockSerializer(1, "\t\t"); - - //Normalize the description and inherit the docs from parents/interfaces - try{ - $this->normalizeDescription($phpdoc, $method); - }catch(\Exception $e){ - $this->info("Cannot normalize method $alias::$methodName.."); - } - - //Correct the return values - $returnValue = $this->getReturn($phpdoc); - - //Get the parameters, including formatted default values - list($params, $paramsWithDefault) = $this->getParameters($method); - - //Make the method static - $phpdoc->appendTag(Tag::createInstance('@static', $phpdoc)); - - //Write the output, using the DocBlock serializer - $output .= $serializer->getDocComment($phpdoc) ."\n\t\t public static function ".$methodName."("; - - $output .= implode($paramsWithDefault, ", "); - $output .= "){\n"; - - //Only return when not a constructor and not void. - $return = ($returnValue && $returnValue !== "void" && $method->name !== "__construct") ? 'return' : ''; - - //Reference the 'real' function in the declaringclass - $declaringClass = $method->getDeclaringClass(); - $declaringClassName = '\\'.ltrim($declaringClass->name, '\\'); - $root = '\\'.ltrim($class->getName(), '\\'); - - if($declaringClass->name != $root){ - $output .= "\t\t\t//Method inherited from $declaringClassName\n"; - } - - $output .= "\t\t\t$return $root::"; - - //Write the default parameters in the function call - $output .= $method->name."(".implode($params, ", ").");\n"; - $output .= "\t\t }\n\n"; - - return $output; - } - - /** - * Get the description and get the inherited docs. - * - * @param $phpdoc - * @param $method - */ - protected function normalizeDescription(&$phpdoc, $method){ - //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($method); - $inheritDescription = $inheritdoc->getText(); - - $description = str_replace('{@inheritdoc}', $inheritDescription, $description); - $phpdoc->setText($description); - - //Add the tags that are inherited - $inheritTags = $inheritdoc->getTags(); - if($inheritTags){ - foreach($inheritTags as $tag){ - $tag->setDocBlock(); - $phpdoc->appendTag($tag); - } - } - } - } - - /** - * Make some changes to the return types, if needed. - * - * @param $phpdoc - * @return string|null - */ - protected function getReturn($phpdoc){ - //Get the return type and adjust them for beter autocomplete - $returnTags = $phpdoc->getTagsByName('return'); - if($returnTags){ - /** @var $tag */ - $tag = reset($returnTags); - $returnValue = $tag->getType(); - }else{ - $returnValue = null; - } - return $returnValue; - } - - /** - * 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->getDefaultValue(); - 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; - }else{ - $default = "'".trim($default)."'"; - } - $paramStr .= " = $default"; - } - $paramsWithDefault[] = $paramStr; - } - return array($params, $paramsWithDefault); - } - - /** - * @param \ReflectionMethod $reflectionMethod - * @return string - */ - 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){ - $phpdoc = new DocBlock($method); - 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/src/Console/ModelsCommand.php b/src/Console/ModelsCommand.php index 03a6bdb..5f26a2a 100644 --- a/src/Console/ModelsCommand.php +++ b/src/Console/ModelsCommand.php @@ -25,7 +25,8 @@ use phpDocumentor\Reflection\DocBlock\Serializer as DocBlockSerializer; * * @author Barry vd. Heuvel */ -class ModelsCommand extends Command { +class ModelsCommand extends Command +{ /** * The console command name. @@ -49,7 +50,6 @@ class ModelsCommand extends Command { protected $reset; - /** * Execute the console command. * @@ -59,25 +59,31 @@ class ModelsCommand extends Command { { $filename = $this->option('filename'); $this->write = $this->option('write'); - $this->dirs = array_merge($this->laravel['config']->get('laravel-ide-helper::model_locations'), $this->option('dir')); + $this->dirs = array_merge( + $this->laravel['config']->get('laravel-ide-helper::model_locations'), + $this->option('dir') + ); $model = $this->argument('model'); $ignore = $this->option('ignore'); $this->reset = $this->option('reset'); //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): ")){ + 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): " + ) + ) { $this->write = true; } } $content = $this->generateDocs($model, $ignore); - if(!$this->write){ + if (!$this->write) { $written = \File::put($filename, $content); - if($written !== false){ + if ($written !== false) { $this->info("Model information was written to $filename"); - }else{ + } else { $this->error("Failed to write model information to $filename"); } } @@ -113,7 +119,8 @@ class ModelsCommand extends Command { ); } - protected function generateDocs($loadModels, $ignore = ''){ + protected function generateDocs($loadModels, $ignore = '') + { $output = "loadModels(); - }else{ + } else { $models = array(); - foreach($loadModels as $model){ + foreach ($loadModels as $model) { $models = array_merge($models, explode(',', $model)); } } $ignore = explode(',', $ignore); - foreach($models as $name){ - if(in_array($name, $ignore)){ + foreach ($models as $name) { + if (in_array($name, $ignore)) { $this->comment("Ignoring model '$name'"); continue; - }else{ + } else { $this->comment("Loading model '$name'"); } $this->properties = array(); $this->methods = array(); - if(class_exists($name)){ + if (class_exists($name)) { try { // handle abstract classes, interfaces, ... $reflectionClass = new \ReflectionClass($name); if (!$reflectionClass->IsInstantiable()) { throw new \Exception($name . ' is not instanciable.'); - }elseif(!$reflectionClass->isSubclassOf('Illuminate\Database\Eloquent\Model')){ + } elseif (!$reflectionClass->isSubclassOf('Illuminate\Database\Eloquent\Model')) { $this->comment("Class '$name' is not a model"); continue; } $model = new $name(); - if($hasDoctrine){ + if ($hasDoctrine) { $this->getPropertiesFromTable($model); } $this->getPropertiesFromMethods($model); $output .= $this->createPhpDocs($name); - }catch(\Exception $e){ - $this->error("Exception: ".$e->getMessage()."\nCould not analyze class $name."); + } catch (\Exception $e) { + $this->error("Exception: " . $e->getMessage() . "\nCould not analyze class $name."); } - }else{ + } else { $this->error("Class $name does not exist"); } } - - 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'."); + + 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'." + ); } return $output; @@ -183,12 +192,13 @@ class ModelsCommand extends Command { } - protected function loadModels(){ + protected function loadModels() + { $models = array(); - foreach($this->dirs as $dir){ + foreach ($this->dirs as $dir) { $dir = base_path() . '/' . $dir; - if(file_exists($dir)){ - foreach(ClassMapGenerator::createMap($dir) as $model=> $path){ + if (file_exists($dir)) { + foreach (ClassMapGenerator::createMap($dir) as $model => $path) { $models[] = $model; } } @@ -201,21 +211,22 @@ class ModelsCommand extends Command { * * @param \Illuminate\Database\Eloquent\Model $model */ - protected function getPropertiesFromTable($model){ + protected function getPropertiesFromTable($model) + { $table = $model->getConnection()->getTablePrefix() . $model->getTable(); $schema = $model->getConnection()->getDoctrineSchemaManager($table); $schema->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string'); $columns = $schema->listTableColumns($table); - if($columns){ + if ($columns) { foreach ($columns as $column) { $name = $column->getName(); - if(in_array($name, $model->getDates())){ + if (in_array($name, $model->getDates())) { $type = '\Carbon\Carbon'; - }else{ - $type = $column->getType()->getName(); - switch($type){ + } else { + $type = $column->getType()->getName(); + switch ($type) { case 'string': case 'text': case 'date': @@ -245,7 +256,11 @@ class ModelsCommand extends Command { $this->setProperty($name, $type, true, true); - $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') + ); } } } @@ -253,33 +268,42 @@ class ModelsCommand extends Command { /** * @param \Illuminate\Database\Eloquent\Model $model */ - protected function getPropertiesFromMethods($model){ + protected function getPropertiesFromMethods($model) + { $methods = get_class_methods($model); - if($methods){ - foreach($methods as $method){ - if(Str::startsWith($method, 'get') && Str::endsWith($method, 'Attribute') && $method !== 'getAttribute'){ + if ($methods) { + foreach ($methods as $method) { + if (Str::startsWith($method, 'get') && Str::endsWith( + $method, + 'Attribute' + ) && $method !== 'getAttribute' + ) { //Magic getAttribute - $name = Str::snake(substr($method, 3, -9)); - if(!empty($name)){ + $name = Str::snake(substr($method, 3, -9)); + if (!empty($name)) { $this->setProperty($name, null, true, null); } - }elseif(Str::startsWith($method, 'set') && Str::endsWith($method, 'Attribute') && $method !== 'setAttribute'){ + } elseif (Str::startsWith($method, 'set') && Str::endsWith( + $method, + 'Attribute' + ) && $method !== 'setAttribute' + ) { //Magic setAttribute - $name = Str::snake(substr($method, 3, -9)); - if(!empty($name)){ + $name = Str::snake(substr($method, 3, -9)); + if (!empty($name)) { $this->setProperty($name, null, null, true); } - }elseif(Str::startsWith($method, 'scope') && $method !== 'scopeQuery'){ + } elseif (Str::startsWith($method, 'scope') && $method !== 'scopeQuery') { //Magic setAttribute - $name = Str::camel(substr($method, 5)); - if(!empty($name)){ + $name = Str::camel(substr($method, 5)); + if (!empty($name)) { $reflection = new \ReflectionMethod($model, $method); $args = $this->getParameters($reflection); //Remove the first ($query) argument array_shift($args); - $this->setMethod($name, '\\'.$reflection->class, $args); + $this->setMethod($name, '\\' . $reflection->class, $args); } - }elseif(!method_exists('Eloquent', $method) && !Str::startsWith($method, 'get')){ + } elseif (!method_exists('Eloquent', $method) && !Str::startsWith($method, 'get')) { //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php $reflection = new \ReflectionMethod($model, $method); @@ -288,24 +312,37 @@ class ModelsCommand extends Command { $file->seek($reflection->getStartLine() - 1); $code = ''; - while ($file->key() < $reflection->getEndLine()) - { - $code .= $file->current(); $file->next(); + while ($file->key() < $reflection->getEndLine()) { + $code .= $file->current(); + $file->next(); } $begin = strpos($code, 'function('); $code = substr($code, $begin, strrpos($code, '}') - $begin + 1); - foreach(array('hasMany', 'belongsToMany', 'hasOne', 'belongsTo', 'morphTo', 'morphMany', 'morphToMany') as $relation){ - $search = '$this->'.$relation.'('; - if($pos = stripos($code, $search)){ + foreach (array( + 'hasMany', + 'belongsToMany', + 'hasOne', + 'belongsTo', + 'morphTo', + 'morphMany', + 'morphToMany' + ) as $relation) { + $search = '$this->' . $relation . '('; + if ($pos = stripos($code, $search)) { $code = substr($code, $pos + strlen($search)); $arguments = explode(',', substr($code, 0, stripos($code, ')'))); //Remove quotes, ensure 1 \ in front of the model - $returnModel = "\\".ltrim(trim($arguments[0], " \"'"), "\\"); - if($relation === "belongsToMany" or $relation === 'hasMany' or $relation === 'morphMany' or $relation === 'morphToMany'){ + $returnModel = "\\" . ltrim(trim($arguments[0], " \"'"), "\\"); + if ($relation === "belongsToMany" or $relation === 'hasMany' or $relation === 'morphMany' or $relation === 'morphToMany') { //Collection or array of models (because Collection is Arrayable) - $this->setProperty($method, '\Illuminate\Database\Eloquent\Collection|'.$returnModel.'[]', true, null); - }else{ + $this->setProperty( + $method, + '\Illuminate\Database\Eloquent\Collection|' . $returnModel . '[]', + true, + null + ); + } else { //Single model is returned $this->setProperty($method, $returnModel, true, null); } @@ -323,26 +360,28 @@ class ModelsCommand extends Command { * @param bool|null $read * @param bool|null $write */ - protected function setProperty($name, $type = null, $read = null, $write = null){ - if(!isset($this->properties[$name])){ + protected function setProperty($name, $type = null, $read = null, $write = null) + { + if (!isset($this->properties[$name])) { $this->properties[$name] = array(); $this->properties[$name]['type'] = 'mixed'; $this->properties[$name]['read'] = false; $this->properties[$name]['write'] = false; } - if($type !== null){ + if ($type !== null) { $this->properties[$name]['type'] = $type; } - if($read !== null){ + if ($read !== null) { $this->properties[$name]['read'] = $read; } - if($write !== null){ + if ($write !== null) { $this->properties[$name]['write'] = $write; } } - protected function setMethod($name, $type = '', $arguments=array()){ - if(!isset($this->methods[$name])){ + protected function setMethod($name, $type = '', $arguments = array()) + { + if (!isset($this->methods[$name])) { $this->methods[$name] = array(); $this->methods[$name]['type'] = $type; $this->methods[$name]['arguments'] = $arguments; @@ -353,55 +392,56 @@ class ModelsCommand extends Command { * @param string $class * @return string */ - protected function createPhpDocs($class){ + protected function createPhpDocs($class) + { $reflection = new \ReflectionClass($class); $namespace = $reflection->getNamespaceName(); $classname = $reflection->getShortName(); $originalDoc = $reflection->getDocComment(); - if($this->reset){ + if ($this->reset) { $phpdoc = new DocBlock('', new Context($namespace)); - }else{ + } else { $phpdoc = new DocBlock($reflection, new Context($namespace)); } - if(!$phpdoc->getText()){ + if (!$phpdoc->getText()) { $phpdoc->setText($class); } $properties = array(); $methods = array(); - foreach($phpdoc->getTags() as $tag){ + foreach ($phpdoc->getTags() as $tag) { $name = $tag->getName(); - if($name == "property" || $name == "property-read" || $name == "property-write"){ - $properties[] =$tag->getVariableName(); - }elseif($name == "method"){ + if ($name == "property" || $name == "property-read" || $name == "property-write") { + $properties[] = $tag->getVariableName(); + } elseif ($name == "method") { $methods[] = $tag->getMethodName(); } } - foreach($this->properties as $name => $property){ + foreach ($this->properties as $name => $property) { $name = "\$$name"; - if(in_array($name, $properties)){ + if (in_array($name, $properties)) { continue; } - if($property['read'] && $property['write']){ + if ($property['read'] && $property['write']) { $attr = 'property'; - }elseif($property['write']){ + } elseif ($property['write']) { $attr = 'property-write'; - }else{ + } else { $attr = 'property-read'; } $tag = Tag::createInstance("@{$attr} {$property['type']} {$name}", $phpdoc); $phpdoc->appendTag($tag); } - foreach($this->methods as $name => $method){ - if(in_array($name, $methods)){ + foreach ($this->methods as $name => $method) { + if (in_array($name, $methods)) { continue; } - $arguments = implode(', ',$method['arguments']); + $arguments = implode(', ', $method['arguments']); $tag = Tag::createInstance("@method static {$method['type']} {$name}({$arguments}) ", $phpdoc); $phpdoc->appendTag($tag); } @@ -411,21 +451,21 @@ class ModelsCommand extends Command { $docComment = $serializer->getDocComment($phpdoc); - if($this->write){ + if ($this->write) { $filename = $reflection->getFileName(); $contents = \File::get($filename); - if($originalDoc){ + if ($originalDoc) { $contents = str_replace($originalDoc, $docComment, $contents); - }else{ + } else { $needle = "class {$classname}"; $replace = "{$docComment}\nclass {$classname}"; - $pos = strpos($contents,$needle); + $pos = strpos($contents, $needle); if ($pos !== false) { - $contents = substr_replace($contents,$replace,$pos,strlen($needle)); + $contents = substr_replace($contents, $replace, $pos, strlen($needle)); } } - if(\File::put($filename, $contents)){ - $this->info('Written new phpDocBlock to '.$filename); + if (\File::put($filename, $contents)) { + $this->info('Written new phpDocBlock to ' . $filename); } } @@ -439,25 +479,26 @@ class ModelsCommand extends Command { * @param $method * @return array */ - public function getParameters($method){ + 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(); + $paramStr = '$' . $param->getName(); $params[] = $paramStr; if ($param->isOptional()) { $default = $param->getDefaultValue(); - if(is_bool($default)){ - $default = $default? 'true':'false'; - }elseif(is_array($default)){ + if (is_bool($default)) { + $default = $default ? 'true' : 'false'; + } elseif (is_array($default)) { $default = 'array()'; - }elseif(is_null($default)){ + } elseif (is_null($default)) { $default = 'null'; - }elseif(is_int($default)){ + } elseif (is_int($default)) { //$default = $default; - }else{ - $default = "'".trim($default)."'"; + } else { + $default = "'" . trim($default) . "'"; } $paramStr .= " = $default"; } diff --git a/src/Generator.php b/src/Generator.php new file mode 100644 index 0000000..74aced3 --- /dev/null +++ b/src/Generator.php @@ -0,0 +1,130 @@ + + * @copyright 2013 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; + +class Generator +{ + + /** @var \Illuminate\View\Factory */ + protected $view; + + protected $extra; + protected $magic; + protected $helpers; + + /** + * @param \Illuminate\View\Factory $view + * @param array $extra + * @param array $magic + * @param string $helpers + */ + public function __construct( /* Illuminate\View\Factory */ + $view, + $extra = array(), + $magic = array(), + $helpers = '' + ) { + $this->view = $view; + $this->extra = $extra; + $this->magic = $magic; + $this->helpers = $helpers; + } + + /** + * Generate the helper file contents; + * + * @return string; + */ + public function generate() + { + return $this->view->make('laravel-ide-helper::ide-helper') + ->with('namespaces', $this->getNamespaces()) + ->with('helpers', $this->helpers) + ->render(); + + } + + /** + * Find all namespaces/aliases that are valid for us to render + * + * @return array + */ + protected function getNamespaces() + { + $namespaces = array(); + + // Get all aliases + foreach (AliasLoader::getInstance()->getAliases() as $name => $facade) { + $alias = new Alias($name, $facade); + + if ($alias->isValid()) { + + $driver = $this->getDriver($name); + if ($driver) { + $alias->addClass($driver); + } + + //Add extra methods, from other classes (magic static calls) + if (array_key_exists($name, $this->extra)) { + $alias->addClass($this->extra[$name]); + } + + //Add extra methods, from other classes (magic static calls) + if (array_key_exists($name, $this->magic)) { + $alias->addClass($this->magic[$name]); + } + + $namespace = $alias->getNamespace(); + if (!isset($namespaces[$namespace])) { + $namespaces[$namespace] = array(); + } + $namespaces[$namespace][] = $alias; + } + + } + + return $namespaces; + } + + /** + * 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; + } + } + +} diff --git a/src/IdeHelperServiceProvider.php b/src/IdeHelperServiceProvider.php index 3e267d8..53edefb 100644 --- a/src/IdeHelperServiceProvider.php +++ b/src/IdeHelperServiceProvider.php @@ -12,55 +12,61 @@ namespace Barryvdh\LaravelIdeHelper; use Illuminate\Support\ServiceProvider; use Barryvdh\LaravelIdeHelper\Console\GeneratorCommand; +use Barryvdh\LaravelIdeHelper\Console\Generator2Command; Use Barryvdh\LaravelIdeHelper\Console\ModelsCommand; -class IdeHelperServiceProvider extends ServiceProvider { +class IdeHelperServiceProvider extends ServiceProvider +{ - /** - * Indicates if loading of the provider is deferred. - * - * @var bool - */ - protected $defer = true; + /** + * Indicates if loading of the provider is deferred. + * + * @var bool + */ + protected $defer = true; - /** - * Bootstrap the application events. - * - * @return void - */ - public function boot() - { - $this->app['config']->package('barryvdh/laravel-ide-helper', __DIR__ . '/config'); - } + /** + * Bootstrap the application events. + * + * @return void + */ + public function boot() + { + $this->package('barryvdh/laravel-ide-helper', 'laravel-ide-helper', __DIR__); + } - /** - * Register the service provider. - * - * @return void - */ - public function register() - { - $this->app['command.ide-helper.generate'] = $this->app->share(function() - { - return new GeneratorCommand; - }); + /** + * Register the service provider. + * + * @return void + */ + public function register() + { - $this->app['command.ide-helper.models'] = $this->app->share(function() - { + $this->app['command.ide-helper.generate'] = $this->app->share( + function ($app) { + return new GeneratorCommand($app['config'], $app['files'], $app['view']); + } + ); + + + $this->app['command.ide-helper.models'] = $this->app->share( + function () { return new ModelsCommand(); - }); + } + ); $this->commands('command.ide-helper.generate', 'command.ide-helper.models'); - } + } - /** - * Get the services provided by the provider. - * - * @return array - */ - public function provides() - { + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { return array('command.ide-helper.generate', 'command.ide-helper.models'); - } + } } diff --git a/src/Method.php b/src/Method.php new file mode 100644 index 0000000..2e7989b --- /dev/null +++ b/src/Method.php @@ -0,0 +1,246 @@ + + * @copyright 2013 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\Serializer as DocBlockSerializer; + +class Method +{ + + protected $output = ''; + protected $name; + protected $namespace; + protected $phpdoc; + protected $params = array(); + protected $params_with_default = array(); + + public function __construct($method, $alias, $class, $methodName = null) + { + + $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->normalizeDescription($method); + } catch (\Exception $e) { + $this->info("Cannot normalize method $alias::$methodName.."); + } + + //Get the parameters, including formatted default values + $this->getParameters($method); + + //Make the method static + $this->phpdoc->appendTag(Tag::createInstance('@static', $this->phpdoc)); + + //Correct the return values + $returnValue = $this->getReturn(); + //Only return when not a constructor and not void. + $this->return = ($returnValue && $returnValue !== "void" && $method->name !== "__construct"); + + //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; + } + + /** + * Should the function return a value? + * + * @return bool + */ + public function shouldReturn() + { + return $this->return; + } + + /** + * 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(', ', $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(', ', $this->params_with_default); + } + + /** + * Get the description and get the inherited docs. + * + * @param $method + */ + protected function normalizeDescription($method) + { + //Get the short + long description from the DocBlock + $description = $this->phpdoc->getText(); + + //Loop through parents/interfaces, to fill in {@inheritdoc} + if (strpos($description, '{@inheritdoc}') !== false) { + $inheritdoc = $this->getInheritDoc($method); + $inheritDescription = $inheritdoc->getText(); + + $description = str_replace('{@inheritdoc}', $inheritDescription, $description); + $this->phpdoc->setText($description); + + //Add the tags that are inherited + $inheritTags = $inheritdoc->getTags(); + if ($inheritTags) { + foreach ($inheritTags as $tag) { + $tag->setDocBlock(); + $this->phpdoc->appendTag($tag); + } + } + } + } + + /** + * Make some changes to the return types, if needed. + * + * @return string|null + */ + protected function getReturn() + { + //Get the return type and adjust them for beter autocomplete + $returnTags = $this->phpdoc->getTagsByName('return'); + if ($returnTags) { + /** @var $tag */ + $tag = reset($returnTags); + $returnValue = $tag->getType(); + } else { + $returnValue = null; + } + return $returnValue; + } + + /** + * 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->getDefaultValue(); + 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; + } else { + $default = "'" . trim($default) . "'"; + } + $paramStr .= " = $default"; + } + $paramsWithDefault[] = $paramStr; + } + + $this->params = $params; + $this->params_with_default = $paramsWithDefault; + } + + /** + * @param \ReflectionMethod $reflectionMethod + * @return string + */ + 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) { + $phpdoc = new DocBlock($method); + 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/src/config/config.php b/src/config/config.php old mode 100755 new mode 100644 diff --git a/src/views/ide-helper.php b/src/views/ide-helper.php new file mode 100644 index 0000000..6b15543 --- /dev/null +++ b/src/views/ide-helper.php @@ -0,0 +1,36 @@ + + +/** + * An helper file for Laravel 4, to provide autocomplete information to your IDE + * Generated with https://github.com/barryvdh/laravel-ide-helper + * + * @author Barry vd. Heuvel + */ + + $aliases): ?> + +namespace { + + + + + + getClassType() ?> getAlias() ?> getExtends() ? 'extends ' . $alias->getExtends() : '' ?>{ + getMethods() as $method): ?> + + getDocComment()) ?> + + public static function getName() ?>(getParamsWithDefault() ?>){ + //Method inherited from getDeclaringClass() ?> + + shouldReturn() ? 'return ': '' ?>getRoot() ?>::getName() ?>(getParams() ?>); + } + + + } + + + +} + +