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.
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).
With the -M option, you can generate just one model.
`php artisan ide-helper:models -M Post`
For now, only models in app/models are scanned. The optional argument tells what models to use.
`php artisan ide-helper:models Post,User`
+137 -9
View File
@@ -33,8 +33,8 @@ class ModelsCommand extends Command {
*/
public function fire()
{
$filename = $this->argument('filename');
$model = $this->option('model');
$filename = $this->option('filename');
$model = $this->argument('model');
$content = $this->generateDocs($model);
@@ -57,7 +57,8 @@ class ModelsCommand extends Command {
protected function getArguments()
{
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()
{
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 === '*'){
$models = $this->loadModels();
}else{
$models = array($model);
$models = explode(',', $model);
}
foreach($models as $name){
$this->properties = array();
if(class_exists($name)){
try{
$model = new $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;
@@ -107,9 +115,8 @@ class ModelsCommand extends Command {
protected function loadModels(){
$models = array();
foreach(\File::files(app_path().'/models') as $file){
list($name, $ext) = explode('.', basename($file));
$models[] = ucfirst($name);
foreach(self::createMap(app_path().'/models') as $model=> $path){
$models[] = $model;
}
return $models;
}
@@ -121,6 +128,7 @@ class ModelsCommand extends Command {
$columns = $schema->listTableColumns($table);
$properties = array();
if($columns){
foreach ($columns as $column) {
$name = $column->getName();
$type = $column->getType()->getName();
@@ -155,9 +163,12 @@ class ModelsCommand extends Command {
$this->setProperty($name, $type, true, true);
}
}
}
protected function getPropertiesFromMethods($model){
foreach(get_class_methods($model) as $method){
$methods = get_class_methods($model);
if($methods){
foreach($methods as $method){
if(\Str::startsWith($method, 'get') && \Str::endsWith($method, 'Attribute') && $method !== 'setAttribute'){
//Magic get<name>Attribute
$name = \Str::snake(substr($method, 3, -9));
@@ -176,6 +187,7 @@ class ModelsCommand extends Command {
}
}
}
}
protected function setProperty($name, $type = null, $read = null, $write = null){
if(!isset($this->properties[$name])){
@@ -216,4 +228,120 @@ class ModelsCommand extends Command {
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;
}
}