Work on Model Docs

Use classmapper from composer and add some checks to make sure the class
exist.
Also, tweak argument/options and filename.
This commit is contained in:
Barry vd. Heuvel
2013-04-29 10:16:33 +02:00
parent 40d9dfd32c
commit 7160a37e49
3 changed files with 191 additions and 64 deletions
+2 -3
View File
@@ -46,8 +46,7 @@ If you don't want to write your properties yourself, you can use the (experiment
phpDocs, based on table columns, relations and getters/setters. Very alpha, so please provide feedback if you want. phpDocs, based on table columns, relations and getters/setters. Very alpha, so please provide feedback if you want.
Docs are written to a phpfile in the root of the project, so you can move the docs to the real model. Docs are written to a phpfile in the root of the project, so you can move the docs to the real model.
For now, only models in app/models are scanned, with the same name as the file (eg. app/models/user.php -> User). For now, only models in app/models are scanned. The optional argument tells what models to use.
With the -M option, you can generate just one model. `php artisan ide-helper:models Post,User`
`php artisan ide-helper:models -M Post`
@@ -168,7 +168,7 @@ namespace {\n\tdie('Only to be used as an helper for your IDE');\n}\n\n";
if(!in_array($alias, $onlyExtend) || $sublime) if(!in_array($alias, $onlyExtend) || $sublime)
{ {
$methods = $d->getMethods(); $methods = $d->getMethods();
if($methods) if($methods)
{ {
foreach ($methods as $method) foreach ($methods as $method)
{ {
+188 -60
View File
@@ -33,8 +33,8 @@ class ModelsCommand extends Command {
*/ */
public function fire() public function fire()
{ {
$filename = $this->argument('filename'); $filename = $this->option('filename');
$model = $this->option('model'); $model = $this->argument('model');
$content = $this->generateDocs($model); $content = $this->generateDocs($model);
@@ -57,7 +57,8 @@ class ModelsCommand extends Command {
protected function getArguments() protected function getArguments()
{ {
return array( return array(
array('filename', InputArgument::OPTIONAL, 'The path to the helper file', 'model_phpdocs.php'), array('model', InputArgument::OPTIONAL, 'Which models to include', '*'),
); );
} }
@@ -69,7 +70,7 @@ class ModelsCommand extends Command {
protected function getOptions() protected function getOptions()
{ {
return array( return array(
array('model', "M", InputOption::VALUE_OPTIONAL, 'Which models to include', '*'), array('filename', 'F', InputOption::VALUE_OPTIONAL, 'The path to the helper file', '_ide_helper_models.php'),
); );
} }
@@ -89,15 +90,22 @@ class ModelsCommand extends Command {
if($model === '*'){ if($model === '*'){
$models = $this->loadModels(); $models = $this->loadModels();
}else{ }else{
$models = array($model); $models = explode(',', $model);
} }
foreach($models as $name){ foreach($models as $name){
$this->properties = array(); $this->properties = array();
$model = new $name(); if(class_exists($name)){
$this->getPropertiesFromTable($model); try{
$this->getPropertiesFromMethods($model); $model = new $name();
$output .= $this->createPhpDocs($name); $this->getPropertiesFromTable($model);
$this->getPropertiesFromMethods($model);
$output .= $this->createPhpDocs($name);
}catch(\Exception $e){
$this->error("Exception: ".$e->getMessage()."\nCould not analyze class $name.");
}
}
} }
return $output; return $output;
@@ -107,10 +115,9 @@ class ModelsCommand extends Command {
protected function loadModels(){ protected function loadModels(){
$models = array(); $models = array();
foreach(\File::files(app_path().'/models') as $file){ foreach(self::createMap(app_path().'/models') as $model=> $path){
list($name, $ext) = explode('.', basename($file)); $models[] = $model;
$models[] = ucfirst($name); }
}
return $models; return $models;
} }
@@ -121,60 +128,65 @@ class ModelsCommand extends Command {
$columns = $schema->listTableColumns($table); $columns = $schema->listTableColumns($table);
$properties = array(); $properties = array();
foreach ($columns as $column) { if($columns){
$name = $column->getName(); foreach ($columns as $column) {
$type = $column->getType()->getName(); $name = $column->getName();
switch($type){ $type = $column->getType()->getName();
case 'string': switch($type){
case 'text': case 'string':
case 'date': case 'text':
case 'time': case 'date':
case 'guid': case 'time':
$type = 'string'; case 'guid':
break; $type = 'string';
case 'integer': break;
case 'bigint': case 'integer':
case 'smallint': case 'bigint':
$type = 'integer'; case 'smallint':
break; $type = 'integer';
case 'decimal': break;
case 'float': case 'decimal':
$type = 'float'; case 'float':
break; $type = 'float';
case 'boolean': break;
$type = 'boolean'; case 'boolean':
break; $type = 'boolean';
case 'datetimetz': //String or DateTime, depending on $dates break;
case 'datetime': case 'datetimetz': //String or DateTime, depending on $dates
$type = 'string|DateTime'; case 'datetime':
break; $type = 'string|DateTime';
default: break;
$type = 'mixed'; default:
break; $type = 'mixed';
break;
}
$this->setProperty($name, $type, true, true);
} }
$this->setProperty($name, $type, true, true);
} }
} }
protected function getPropertiesFromMethods($model){ protected function getPropertiesFromMethods($model){
foreach(get_class_methods($model) as $method){ $methods = get_class_methods($model);
if(\Str::startsWith($method, 'get') && \Str::endsWith($method, 'Attribute') && $method !== 'setAttribute'){ if($methods){
//Magic get<name>Attribute foreach($methods as $method){
$name = \Str::snake(substr($method, 3, -9)); if(\Str::startsWith($method, 'get') && \Str::endsWith($method, 'Attribute') && $method !== 'setAttribute'){
if(!empty($name)){ //Magic get<name>Attribute
$this->setProperty($name, null, true, null); $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'){
//Magic set<name>Attribute
$name = \Str::snake(substr($method, 3, -9));
if(!empty($name)){
$this->setProperty($name, null, null, true);
}
}elseif(!method_exists('Eloquent', $method) && !\Str::startsWith($method, 'get')){
//If not declared in parent class, assuming relation.
$this->setProperty($method, 'Eloquent', true, null);
} }
}elseif(\Str::startsWith($method, 'set') && \Str::endsWith($method, 'Attribute') && $method !== 'setAttribute'){
//Magic set<name>Attribute
$name = \Str::snake(substr($method, 3, -9));
if(!empty($name)){
$this->setProperty($name, null, null, true);
}
}elseif(!method_exists('Eloquent', $method) && !\Str::startsWith($method, 'get')){
//If not declared in parent class, assuming relation.
$this->setProperty($method, 'Eloquent', true, null);
} }
} }
} }
protected function setProperty($name, $type = null, $read = null, $write = null){ protected function setProperty($name, $type = null, $read = null, $write = null){
@@ -216,4 +228,120 @@ class ModelsCommand extends Command {
return $output; return $output;
} }
/**
* Copy from Composer\Autoload\ClassMapGenerator
* @author Gyula Sallai <[email protected]>
*
* Iterate over all files in the given directory searching for classes
*
* @param Iterator|string $path The path to search in or an iterator
* @param string $whitelist Regex that matches against the file path
*
* @return array A class map array
*
* @throws \RuntimeException When the path is neither an existing file nor directory
*/
public static function createMap($path, $whitelist = null)
{
if (is_string($path)) {
if (is_file($path)) {
$path = array(new \SplFileInfo($path));
} elseif (is_dir($path)) {
$path = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
} else {
throw new \RuntimeException(
'Could not scan for classes inside "'.$path.
'" which does not appear to be a file nor a folder'
);
}
}
$map = array();
foreach ($path as $file) {
if (!$file->isFile()) {
continue;
}
$filePath = $file->getRealPath();
if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), array('php', 'inc'))) {
continue;
}
if ($whitelist && !preg_match($whitelist, strtr($filePath, '\\', '/'))) {
continue;
}
$classes = self::findClasses($filePath);
foreach ($classes as $class) {
$map[$class] = $filePath;
}
}
return $map;
}
/**
* Extract the classes in the given file
*
* @param string $path The file to check
*
* @return array The found classes
*/
private static function findClasses($path)
{
$traits = version_compare(PHP_VERSION, '5.4', '<') ? '' : '|trait';
try {
$contents = php_strip_whitespace($path);
} catch (\Exception $e) {
throw new \RuntimeException('Could not scan for classes inside '.$path.": \n".$e->getMessage(), 0, $e);
}
// return early if there is no chance of matching anything in this file
if (!preg_match('{\b(?:class|interface'.$traits.')\b}i', $contents)) {
return array();
}
// strip heredocs/nowdocs
$contents = preg_replace('{<<<\'?(\w+)\'?(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\1(?=\r\n|\n|\r|;)}s', 'null', $contents);
// strip strings
$contents = preg_replace('{"[^"\\\\]*(\\\\.[^"\\\\]*)*"|\'[^\'\\\\]*(\\\\.[^\'\\\\]*)*\'}', 'null', $contents);
// strip leading non-php code if needed
if (substr($contents, 0, 2) !== '<?') {
$contents = preg_replace('{^.+?<\?}s', '<?', $contents);
}
// strip non-php blocks in the file
$contents = preg_replace('{\?>.+<\?}s', '?><?', $contents);
// strip trailing non-php code if needed
$pos = strrpos($contents, '?>');
if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) {
$contents = substr($contents, 0, $pos);
}
preg_match_all('{
(?:
\b(?<![\$:>])(?P<type>class|interface'.$traits.') \s+ (?P<name>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)
| \b(?<![\$:>])(?P<ns>namespace) (?P<nsname>\s+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\s*\\\\\s*[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*)? \s*[\{;]
)
}ix', $contents, $matches);
$classes = array();
$namespace = '';
for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
if (!empty($matches['ns'][$i])) {
$namespace = str_replace(array(' ', "\t", "\r", "\n"), '', $matches['nsname'][$i]) . '\\';
} else {
$classes[] = ltrim($namespace . $matches['name'][$i], '\\');
}
}
return $classes;
}
} }