From 051a461a1d6475b0b070200a2a61561fce69251b Mon Sep 17 00:00:00 2001 From: weskiller Date: Wed, 15 Apr 2020 17:04:44 +0800 Subject: [PATCH 1/4] Feature gen:model relation property --- composer.json | 1 + .../src/Commands/Ast/ModelUpdateVisitor.php | 377 +++++++++++++++++- src/database/src/Commands/ModelCommand.php | 1 + 3 files changed, 368 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 440d34667..4ca8dae55 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ }, "require-dev": { "bandwidth-throttle/token-bucket": "^2.0", + "barryvdh/reflection-docblock": "^2.0", "doctrine/annotations": "^1.6", "doctrine/common": "@stable", "doctrine/inflector": "^1.3", diff --git a/src/database/src/Commands/Ast/ModelUpdateVisitor.php b/src/database/src/Commands/Ast/ModelUpdateVisitor.php index b4b2a21eb..bbb6a5a61 100644 --- a/src/database/src/Commands/Ast/ModelUpdateVisitor.php +++ b/src/database/src/Commands/Ast/ModelUpdateVisitor.php @@ -12,7 +12,22 @@ declare(strict_types=1); namespace Hyperf\Database\Commands\Ast; +use Barryvdh\Reflection\DocBlock; use Hyperf\Database\Commands\ModelOption; +use Hyperf\Database\Model\Builder; +use Hyperf\Database\Model\Collection; +use Hyperf\Database\Model\Model; +use Hyperf\Database\Model\Relations\BelongsTo; +use Hyperf\Database\Model\Relations\BelongsToMany; +use Hyperf\Database\Model\Relations\HasMany; +use Hyperf\Database\Model\Relations\HasManyThrough; +use Hyperf\Database\Model\Relations\HasOne; +use Hyperf\Database\Model\Relations\HasOneThrough; +use Hyperf\Database\Model\Relations\MorphMany; +use Hyperf\Database\Model\Relations\MorphOne; +use Hyperf\Database\Model\Relations\MorphTo; +use Hyperf\Database\Model\Relations\MorphToMany; +use Hyperf\Database\Model\Relations\Relation; use Hyperf\Utils\Str; use PhpParser\Comment\Doc; use PhpParser\Node; @@ -20,6 +35,11 @@ use PhpParser\NodeVisitorAbstract; class ModelUpdateVisitor extends NodeVisitorAbstract { + /** + * @var string + */ + protected $class; + /** * @var array */ @@ -30,31 +50,36 @@ class ModelUpdateVisitor extends NodeVisitorAbstract */ protected $option; - public function __construct($columns = [], ModelOption $option) + /** + * @var array + */ + protected $methods = []; + + /** + * @var array + */ + protected $properties = []; + + public function __construct($class,$columns, ModelOption $option) { + $this->class = $class; $this->columns = $columns; $this->option = $option; + $this->getPropertiesFromMethods(new $this->class); } public function leaveNode(Node $node) { switch ($node) { case $node instanceof Node\Stmt\PropertyProperty: - if ($node->name == 'fillable' && $this->option->isRefreshFillable()) { + if ((string)$node->name === 'fillable' && $this->option->isRefreshFillable()) { $node = $this->rewriteFillable($node); - } elseif ($node->name == 'casts') { + } elseif ((string)$node->name === 'casts') { $node = $this->rewriteCasts($node); } - return $node; case $node instanceof Node\Stmt\Class_: - $doc = '/**' . PHP_EOL; - foreach ($this->columns as $column) { - [$name, $type, $comment] = $this->getProperty($column); - $doc .= sprintf(' * @property %s $%s %s', $type, $name, $comment) . PHP_EOL; - } - $doc .= ' */'; - $node->setDocComment(new Doc($doc)); + $node->setDocComment(new Doc($this->parseProperty())); return $node; } } @@ -92,6 +117,336 @@ class ModelUpdateVisitor extends NodeVisitorAbstract return $node; } + protected function parseProperty() :string + { + $doc = '/**' . PHP_EOL; + foreach ($this->columns as $column) { + [$name, $type, $comment] = $this->getProperty($column); + $doc .= sprintf(' * @property %s $%s %s', $type, $name, $comment) . PHP_EOL; + } + foreach ($this->properties as $name => $property) { + if($property['read'] && $property['write']) { + $doc .= sprintf(' * @property %s $%s', $property['type'], $name) . PHP_EOL; + continue; + } + if($property['read']) { + $doc .= sprintf(' * @property-read %s $%s', $property['type'], $name) . PHP_EOL; + continue; + } + if($property['write']) { + $doc .= sprintf(' * @property-write %s $%s', $property['type'], $name) . PHP_EOL; + continue; + } + } + $doc .= ' */'; + return $doc; + } + + /** + * @author Barry vd. Heuvel + * @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 + */ + protected function getPropertiesFromMethods($model) + { + /** @var Model $model */ + $methods = get_class_methods($model); + if ($methods) { + sort($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)) { + $reflection = new \ReflectionMethod($model, $method); + $type = $this->getReturnTypeFromDocBlock($reflection); + $this->setProperty($name, $type, true, null); + } + } elseif (Str::startsWith($method, 'set') && Str::endsWith( + $method, + 'Attribute' + ) && $method !== 'setAttribute' + ) { + //Magic setAttribute + $name = Str::snake(substr($method, 3, -9)); + if (!empty($name)) { + $this->setProperty($name, null, null, true); + } + } elseif (Str::startsWith($method, 'scope') && $method !== 'scopeQuery') { + //Magic setAttribute + $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, Builder::class .'|\\' . $reflection->class, $args); + } + } elseif (in_array($method, ['query', 'newQuery', 'newModelQuery'])) { + $reflection = new \ReflectionClass($model); + $builder = get_class($model->newModelQuery()); + + $this->setMethod($method, "\\{$builder}|\\" . $reflection->getName()); + } elseif (!method_exists(Model::class, $method) + && !Str::startsWith($method, 'get') + ) { + //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php + $reflection = new \ReflectionMethod($model, $method); + + if ($returnType = $reflection->getReturnType()) { + $type = $returnType instanceof \ReflectionNamedType + ? $returnType->getName() + : (string)$returnType; + } else { + // php 7.x type or fallback to docblock + $type = (string)$this->getReturnTypeFromDocBlock($reflection); + } + + $file = new \SplFileObject($reflection->getFileName()); + $file->seek($reflection->getStartLine() - 1); + + $code = ''; + while ($file->key() < $reflection->getEndLine()) { + $code .= $file->current(); + $file->next(); + } + $code = trim(preg_replace('/\s\s+/', '', $code)); + $begin = strpos($code, 'function'); + $code = substr($code, $begin, strrpos($code, '}') - $begin + 1); + + foreach (array( + 'hasMany' => HasMany::class, + 'hasManyThrough' => HasManyThrough::class, + 'hasOneThrough' => HasOneThrough::class, + 'belongsToMany' => BelongsToMany::class, + 'hasOne' => HasOne::class, + 'belongsTo' => BelongsTo::class, + 'morphOne' => MorphOne::class, + 'morphTo' => MorphTo::class, + 'morphMany' => MorphMany::class, + 'morphToMany' => MorphToMany::class, + 'morphedByMany' => MorphToMany::class, + ) as $relation => $impl) { + $search = '$this->' . $relation . '('; + if (stripos($code, $search) || $impl === (string)$type) { + //Resolve the relation's model to a Relation object. + $methodReflection = new \ReflectionMethod($model, $method); + if ($methodReflection->getNumberOfParameters()) { + continue; + } + + // Adding constraints requires reading model properties which + // can cause errors. Since we don't need constraints we can + // disable them when we fetch the relation to avoid errors. + $relationObj = Relation::noConstraints(function () use ($model, $method) { + return $model->$method(); + }); + + if ($relationObj instanceof Relation) { + $relatedModel = '\\' . get_class($relationObj->getRelated()); + + $relations = [ + 'hasManyThrough', + 'belongsToMany', + 'hasMany', + 'morphMany', + 'morphToMany', + 'morphedByMany', + ]; + if (strpos(get_class($relationObj), 'Many') !== false) { + //Collection or array of models (because Collection is Arrayable) + $this->setProperty( + $method, + $this->getCollectionClass($relatedModel) . '|' . $relatedModel . '[]', + true, + null + ); + /* + $this->setProperty( + Str::snake($method) . '_count', + 'int|null', + true, + false + ); + */ + } elseif ($relation === "morphTo") { + // Model isn't specified because relation is polymorphic + $this->setProperty( + $method, + '\\' .Model::class, + true, + null + ); + } else { + //Single model is returned + $this->setProperty( + $method, + $relatedModel, + true, + null, + '', + $this->isRelationForeignKeyNullable($relationObj) + ); + } + } + } + } + } + } + } + } + + /** + * @author Barry vd. Heuvel + * @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 + */ + private function isRelationForeignKeyNullable(Relation $relation) + { + $reflectionObj = new \ReflectionObject($relation); + if (!$reflectionObj->hasProperty('foreignKey')) { + return false; + } + $fkProp = $reflectionObj->getProperty('foreignKey'); + $fkProp->setAccessible(true); + + return isset($this->nullableColumns[$fkProp->getValue($relation)]); + } + + /** + * @author Barry vd. Heuvel + * @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 + */ + protected function setProperty($name, $type = null, $read = null, $write = null, $comment = '', $nullable = false) + { + if (!isset($this->properties[$name])) { + $this->properties[$name] = array(); + $this->properties[$name]['type'] = 'mixed'; + $this->properties[$name]['read'] = false; + $this->properties[$name]['write'] = false; + $this->properties[$name]['comment'] = (string) $comment; + } + if ($type !== null) { + $newType = $this->getTypeOverride($type); + if ($nullable) { + $newType .='|null'; + } + $this->properties[$name]['type'] = $newType; + } + if ($read !== null) { + $this->properties[$name]['read'] = $read; + } + if ($write !== null) { + $this->properties[$name]['write'] = $write; + } + } + + protected function getTypeOverride($type) + { + //just for compatibility + $typeOverrides = config('devtool.model.type_overrides', []); + + return isset($typeOverrides[$type]) ? $typeOverrides[$type] : $type; + } + + /** + * @author Barry vd. Heuvel + * @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 + */ + protected function setMethod($name, $type = '', $arguments = array()) + { + $methods = array_change_key_case($this->methods, CASE_LOWER); + + if (!isset($methods[strtolower($name)])) { + $this->methods[$name] = array(); + $this->methods[$name]['type'] = $type; + $this->methods[$name]['arguments'] = $arguments; + } + } + + /** + * @author Barry vd. Heuvel + * @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 + */ + public function getParameters(ReflectionMethod $method) + { + //Loop through the default values for paremeters, and make the correct output string + $params = array(); + $paramsWithDefault = array(); + /** @var \ReflectionParameter $param */ + foreach ($method->getParameters() as $param) { + $paramClass = $param->getClass(); + $paramStr = (!is_null($paramClass) ? '\\' . $paramClass->getName() . ' ' : '') . '$' . $param->getName(); + $params[] = $paramStr; + if ($param->isOptional() && $param->isDefaultValueAvailable()) { + $default = $param->getDefaultValue(); + if (is_bool($default)) { + $default = $default ? 'true' : 'false'; + } elseif (is_array($default)) { + $default = '[]'; + } elseif (is_null($default)) { + $default = 'null'; + } elseif (is_int($default)) { + //$default = $default; + } else { + $default = "'" . trim($default) . "'"; + } + $paramStr .= " = $default"; + } + $paramsWithDefault[] = $paramStr; + } + return $paramsWithDefault; + } + + /** + * @author Barry vd. Heuvel + * @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 + */ + private function getCollectionClass($className) + { + // Return something in the very very unlikely scenario the model doesn't + // have a newCollection() method. + if (!method_exists($className, 'newCollection')) { + return Collection::class; + } + + /** @var Model $model */ + $model = new $className; + return '\\' . get_class($model->newCollection()); + } + + /** + * @author Barry vd. Heuvel + * @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 + */ + protected function getReturnTypeFromDocBlock(\ReflectionMethod $reflection) + { + $type = null; + $phpdoc = new DocBlock($reflection); + + if ($phpdoc->hasTag('return')) { + $type = $phpdoc->getTagsByName('return')[0]->getType(); + } + + return $type; + } + protected function getProperty($column): array { $name = $this->option->isCamelCase() ? Str::camel($column['column_name']) : $column['column_name']; diff --git a/src/database/src/Commands/ModelCommand.php b/src/database/src/Commands/ModelCommand.php index 83de02f1c..0ae5a5ec0 100644 --- a/src/database/src/Commands/ModelCommand.php +++ b/src/database/src/Commands/ModelCommand.php @@ -184,6 +184,7 @@ class ModelCommand extends Command $stms = $this->astParser->parse(file_get_contents($path)); $traverser = new NodeTraverser(); $traverser->addVisitor(make(ModelUpdateVisitor::class, [ + 'class' => $class, 'columns' => $columns, 'option' => $option, ])); From 8df311dbe567c99f3d9f58b65947a580efae75af Mon Sep 17 00:00:00 2001 From: weskiller Date: Thu, 16 Apr 2020 12:14:57 +0800 Subject: [PATCH 2/4] Fixed command describe:route if uri same will overlapping --- src/devtool/src/Describe/RoutesCommand.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/devtool/src/Describe/RoutesCommand.php b/src/devtool/src/Describe/RoutesCommand.php index 31f177b00..da47d2d31 100644 --- a/src/devtool/src/Describe/RoutesCommand.php +++ b/src/devtool/src/Describe/RoutesCommand.php @@ -9,7 +9,6 @@ declare(strict_types=1); * @contact group@hyperf.io * @license https://github.com/hyperf/hyperf/blob/master/LICENSE */ - namespace Hyperf\Devtool\Describe; use Hyperf\Command\Annotation\Command; @@ -102,15 +101,16 @@ class RoutesCommand extends HyperfCommand } else { $action = $handler->callback; } - if (isset($data[$uri])) { - $data[$uri]['method'][] = $method; + $unique = "{$serverName}|{$action}"; + if (isset($data[$unique])) { + $data[$unique]['method'][] = $method; } else { // method,uri,name,action,middleware $registedMiddlewares = MiddlewareManager::get($serverName, $uri, $method); $middlewares = $this->config->get('middlewares.' . $serverName, []); $middlewares = array_merge($middlewares, $registedMiddlewares); - $data[$uri] = [ + $data[$unique] = [ 'server' => $serverName, 'method' => [$method], 'uri' => $uri, From 25c4abb4cbf4e75d3b87439eaacca49e06a08def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=93=AD=E6=98=95?= <715557344@qq.com> Date: Mon, 20 Apr 2020 12:33:43 +0800 Subject: [PATCH 3/4] Optimized command `gen:command`. --- src/database/composer.json | 2 +- .../src/Commands/Ast/ModelUpdateVisitor.php | 430 ++++++------------ 2 files changed, 152 insertions(+), 280 deletions(-) diff --git a/src/database/composer.json b/src/database/composer.json index d25704a7a..55b3e8055 100644 --- a/src/database/composer.json +++ b/src/database/composer.json @@ -30,7 +30,7 @@ }, "suggest": { "doctrine/dbal": "Required to rename columns (^2.6).", - "nikic/php-parser": "Required to use ModelCommand (^4.1)." + "roave/better-reflection": "Required to use ModelCommand (^4.0)." }, "autoload": { "psr-4": { diff --git a/src/database/src/Commands/Ast/ModelUpdateVisitor.php b/src/database/src/Commands/Ast/ModelUpdateVisitor.php index 511d1b10d..a1652fbcf 100644 --- a/src/database/src/Commands/Ast/ModelUpdateVisitor.php +++ b/src/database/src/Commands/Ast/ModelUpdateVisitor.php @@ -11,7 +11,6 @@ declare(strict_types=1); */ namespace Hyperf\Database\Commands\Ast; -use Barryvdh\Reflection\DocBlock; use Hyperf\Database\Commands\ModelOption; use Hyperf\Database\Model\Builder; use Hyperf\Database\Model\Collection; @@ -31,9 +30,28 @@ use Hyperf\Utils\Str; use PhpParser\Comment\Doc; use PhpParser\Node; use PhpParser\NodeVisitorAbstract; +use Roave\BetterReflection\BetterReflection; +use Roave\BetterReflection\Reflection\ReflectionClass; +use Roave\BetterReflection\Reflection\ReflectionMethod; +use Roave\BetterReflection\Reflector\ClassReflector; +use Roave\BetterReflection\TypesFinder\FindReturnType; class ModelUpdateVisitor extends NodeVisitorAbstract { + const RELATION_METHODS = [ + 'hasMany' => HasMany::class, + 'hasManyThrough' => HasManyThrough::class, + 'hasOneThrough' => HasOneThrough::class, + 'belongsToMany' => BelongsToMany::class, + 'hasOne' => HasOne::class, + 'belongsTo' => BelongsTo::class, + 'morphOne' => MorphOne::class, + 'morphTo' => MorphTo::class, + 'morphMany' => MorphMany::class, + 'morphToMany' => MorphToMany::class, + 'morphedByMany' => MorphToMany::class, + ]; + /** * @var string */ @@ -59,21 +77,33 @@ class ModelUpdateVisitor extends NodeVisitorAbstract */ protected $properties = []; - public function __construct($class,$columns, ModelOption $option) + /** + * @deprecated v2.0 + * @var ClassReflector + */ + protected static $reflector; + + /** + * @deprecated v2.0 + * @var FindReturnType + */ + protected static $return; + + public function __construct($class, $columns, ModelOption $option) { $this->class = $class; $this->columns = $columns; $this->option = $option; - $this->getPropertiesFromMethods(new $this->class); + $this->initPropertiesFromMethods(); } public function leaveNode(Node $node) { switch ($node) { case $node instanceof Node\Stmt\PropertyProperty: - if ((string)$node->name === 'fillable' && $this->option->isRefreshFillable()) { + if ((string) $node->name === 'fillable' && $this->option->isRefreshFillable()) { $node = $this->rewriteFillable($node); - } elseif ((string)$node->name === 'casts') { + } elseif ((string) $node->name === 'casts') { $node = $this->rewriteCasts($node); } return $node; @@ -116,7 +146,7 @@ class ModelUpdateVisitor extends NodeVisitorAbstract return $node; } - protected function parseProperty() :string + protected function parseProperty(): string { $doc = '/**' . PHP_EOL; foreach ($this->columns as $column) { @@ -124,15 +154,15 @@ class ModelUpdateVisitor extends NodeVisitorAbstract $doc .= sprintf(' * @property %s $%s %s', $type, $name, $comment) . PHP_EOL; } foreach ($this->properties as $name => $property) { - if($property['read'] && $property['write']) { + if ($property['read'] && $property['write']) { $doc .= sprintf(' * @property %s $%s', $property['type'], $name) . PHP_EOL; continue; } - if($property['read']) { + if ($property['read']) { $doc .= sprintf(' * @property-read %s $%s', $property['type'], $name) . PHP_EOL; continue; } - if($property['write']) { + if ($property['write']) { $doc .= sprintf(' * @property-write %s $%s', $property['type'], $name) . PHP_EOL; continue; } @@ -141,158 +171,81 @@ class ModelUpdateVisitor extends NodeVisitorAbstract return $doc; } - /** - * @author Barry vd. Heuvel - * @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 - */ - protected function getPropertiesFromMethods($model) + protected function initPropertiesFromMethods() { - /** @var Model $model */ - $methods = get_class_methods($model); - if ($methods) { - sort($methods); - foreach ($methods as $method) { - if (Str::startsWith($method, 'get') && Str::endsWith( - $method, - 'Attribute' - ) && $method !== 'getAttribute' + /** @var ReflectionClass $reflection */ + $reflection = self::getReflector()->reflect($this->class); + $methods = $reflection->getImmediateMethods(); + $namespace = $reflection->getDeclaringNamespaceAst(); + if (empty($methods)) { + return; + } + + sort($methods); + /** @var ReflectionMethod $method */ + foreach ($methods as $method) { + if (Str::startsWith($method->getName(), 'get') && Str::endsWith($method->getName(), 'Attribute')) { + // Magic getAttribute + $name = Str::snake(substr($method->getName(), 3, -9)); + if (! empty($name)) { + $type = self::getReturnFinder()->__invoke($method, $namespace); + $this->setProperty($name, $type, true, null); + } + continue; + } + + if (Str::startsWith($method->getName(), 'set') && Str::endsWith($method->getName(), 'Attribute')) { + // Magic setAttribute + $name = Str::snake(substr($method->getName(), 3, -9)); + if (! empty($name)) { + $this->setProperty($name, null, null, true); + } + continue; + } + + if (Str::startsWith($method->getName(), 'scope') && $method->getName() !== 'scopeQuery') { + $name = Str::camel(substr($method->getName(), 5)); + if (! empty($name)) { + $args = $method->getParameters(); + // Remove the first ($query) argument + array_shift($args); + $this->setMethod($name, [Builder::class, $method->getDeclaringClass()->getName()], $args); + } + continue; + } + + if ($method->getNumberOfParameters() > 0) { + continue; + } + + $return = $method->getReturnStatementsAst(); + // Magic Relation + if (count($return) === 1 && $return[0] instanceof Node\Stmt\Return_) { + $expr = $return[0]->expr; + if ( + $expr instanceof Node\Expr\MethodCall + && $expr->name instanceof Node\Identifier + && is_string($expr->name->name) + && isset($expr->args[0]) + && $expr->args[0] instanceof Node\Arg ) { - //Magic getAttribute - $name = Str::snake(substr($method, 3, -9)); - if (!empty($name)) { - $reflection = new \ReflectionMethod($model, $method); - $type = $this->getReturnTypeFromDocBlock($reflection); - $this->setProperty($name, $type, true, null); - } - } elseif (Str::startsWith($method, 'set') && Str::endsWith( - $method, - 'Attribute' - ) && $method !== 'setAttribute' - ) { - //Magic setAttribute - $name = Str::snake(substr($method, 3, -9)); - if (!empty($name)) { - $this->setProperty($name, null, null, true); - } - } elseif (Str::startsWith($method, 'scope') && $method !== 'scopeQuery') { - //Magic setAttribute - $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, Builder::class .'|\\' . $reflection->class, $args); - } - } elseif (in_array($method, ['query', 'newQuery', 'newModelQuery'])) { - $reflection = new \ReflectionClass($model); - $builder = get_class($model->newModelQuery()); + $name = $expr->name->name; + if (array_key_exists($name, self::RELATION_METHODS)) { + if ($expr->args[0]->value instanceof Node\Expr\ClassConstFetch) { + $related = $expr->args[0]->value->class->toCodeString(); + } else { + $related = (string) ($expr->args[0]->value); + } - $this->setMethod($method, "\\{$builder}|\\" . $reflection->getName()); - } elseif (!method_exists(Model::class, $method) - && !Str::startsWith($method, 'get') - ) { - //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php - $reflection = new \ReflectionMethod($model, $method); - - if ($returnType = $reflection->getReturnType()) { - $type = $returnType instanceof \ReflectionNamedType - ? $returnType->getName() - : (string)$returnType; - } else { - // php 7.x type or fallback to docblock - $type = (string)$this->getReturnTypeFromDocBlock($reflection); - } - - $file = new \SplFileObject($reflection->getFileName()); - $file->seek($reflection->getStartLine() - 1); - - $code = ''; - while ($file->key() < $reflection->getEndLine()) { - $code .= $file->current(); - $file->next(); - } - $code = trim(preg_replace('/\s\s+/', '', $code)); - $begin = strpos($code, 'function'); - $code = substr($code, $begin, strrpos($code, '}') - $begin + 1); - - foreach (array( - 'hasMany' => HasMany::class, - 'hasManyThrough' => HasManyThrough::class, - 'hasOneThrough' => HasOneThrough::class, - 'belongsToMany' => BelongsToMany::class, - 'hasOne' => HasOne::class, - 'belongsTo' => BelongsTo::class, - 'morphOne' => MorphOne::class, - 'morphTo' => MorphTo::class, - 'morphMany' => MorphMany::class, - 'morphToMany' => MorphToMany::class, - 'morphedByMany' => MorphToMany::class, - ) as $relation => $impl) { - $search = '$this->' . $relation . '('; - if (stripos($code, $search) || $impl === (string)$type) { - //Resolve the relation's model to a Relation object. - $methodReflection = new \ReflectionMethod($model, $method); - if ($methodReflection->getNumberOfParameters()) { - continue; - } - - // Adding constraints requires reading model properties which - // can cause errors. Since we don't need constraints we can - // disable them when we fetch the relation to avoid errors. - $relationObj = Relation::noConstraints(function () use ($model, $method) { - return $model->$method(); - }); - - if ($relationObj instanceof Relation) { - $relatedModel = '\\' . get_class($relationObj->getRelated()); - - $relations = [ - 'hasManyThrough', - 'belongsToMany', - 'hasMany', - 'morphMany', - 'morphToMany', - 'morphedByMany', - ]; - if (strpos(get_class($relationObj), 'Many') !== false) { - //Collection or array of models (because Collection is Arrayable) - $this->setProperty( - $method, - $this->getCollectionClass($relatedModel) . '|' . $relatedModel . '[]', - true, - null - ); - /* - $this->setProperty( - Str::snake($method) . '_count', - 'int|null', - true, - false - ); - */ - } elseif ($relation === "morphTo") { - // Model isn't specified because relation is polymorphic - $this->setProperty( - $method, - '\\' .Model::class, - true, - null - ); - } else { - //Single model is returned - $this->setProperty( - $method, - $relatedModel, - true, - null, - '', - $this->isRelationForeignKeyNullable($relationObj) - ); - } - } + if (strpos($name, 'Many') !== false) { + // Collection or array of models (because Collection is Arrayable) + $this->setProperty($method->getName(), [$this->getCollectionClass($related), $related . '[]'], true); + } elseif ($name === 'morphTo') { + // Model isn't specified because relation is polymorphic + $this->setProperty($method->getName(), [Model::class], true); + } else { + // Single model is returned + $this->setProperty($method->getName(), [$related], true); } } } @@ -300,45 +253,20 @@ class ModelUpdateVisitor extends NodeVisitorAbstract } } - /** - * @author Barry vd. Heuvel - * @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 - */ - private function isRelationForeignKeyNullable(Relation $relation) + protected function setProperty(string $name, array $type = null, bool $read = null, bool $write = null, string $comment = '', bool $nullable = false) { - $reflectionObj = new \ReflectionObject($relation); - if (!$reflectionObj->hasProperty('foreignKey')) { - return false; - } - $fkProp = $reflectionObj->getProperty('foreignKey'); - $fkProp->setAccessible(true); - - return isset($this->nullableColumns[$fkProp->getValue($relation)]); - } - - /** - * @author Barry vd. Heuvel - * @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 - */ - protected function setProperty($name, $type = null, $read = null, $write = null, $comment = '', $nullable = false) - { - if (!isset($this->properties[$name])) { - $this->properties[$name] = array(); + if (! isset($this->properties[$name])) { + $this->properties[$name] = []; $this->properties[$name]['type'] = 'mixed'; $this->properties[$name]['read'] = false; $this->properties[$name]['write'] = false; $this->properties[$name]['comment'] = (string) $comment; } if ($type !== null) { - $newType = $this->getTypeOverride($type); if ($nullable) { - $newType .='|null'; + $type[] = 'null'; } - $this->properties[$name]['type'] = $newType; + $this->properties[$name]['type'] = implode('|', array_unique($type)); } if ($read !== null) { $this->properties[$name]['read'] = $read; @@ -348,104 +276,17 @@ class ModelUpdateVisitor extends NodeVisitorAbstract } } - protected function getTypeOverride($type) - { - //just for compatibility - $typeOverrides = config('devtool.model.type_overrides', []); - - return isset($typeOverrides[$type]) ? $typeOverrides[$type] : $type; - } - - /** - * @author Barry vd. Heuvel - * @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 - */ - protected function setMethod($name, $type = '', $arguments = array()) + protected function setMethod(string $name, array $type = [], array $arguments = []) { $methods = array_change_key_case($this->methods, CASE_LOWER); - if (!isset($methods[strtolower($name)])) { - $this->methods[$name] = array(); - $this->methods[$name]['type'] = $type; + if (! isset($methods[strtolower($name)])) { + $this->methods[$name] = []; + $this->methods[$name]['type'] = implode('|', $type); $this->methods[$name]['arguments'] = $arguments; } } - /** - * @author Barry vd. Heuvel - * @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 - */ - public function getParameters(ReflectionMethod $method) - { - //Loop through the default values for paremeters, and make the correct output string - $params = array(); - $paramsWithDefault = array(); - /** @var \ReflectionParameter $param */ - foreach ($method->getParameters() as $param) { - $paramClass = $param->getClass(); - $paramStr = (!is_null($paramClass) ? '\\' . $paramClass->getName() . ' ' : '') . '$' . $param->getName(); - $params[] = $paramStr; - if ($param->isOptional() && $param->isDefaultValueAvailable()) { - $default = $param->getDefaultValue(); - if (is_bool($default)) { - $default = $default ? 'true' : 'false'; - } elseif (is_array($default)) { - $default = '[]'; - } elseif (is_null($default)) { - $default = 'null'; - } elseif (is_int($default)) { - //$default = $default; - } else { - $default = "'" . trim($default) . "'"; - } - $paramStr .= " = $default"; - } - $paramsWithDefault[] = $paramStr; - } - return $paramsWithDefault; - } - - /** - * @author Barry vd. Heuvel - * @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 - */ - private function getCollectionClass($className) - { - // Return something in the very very unlikely scenario the model doesn't - // have a newCollection() method. - if (!method_exists($className, 'newCollection')) { - return Collection::class; - } - - /** @var Model $model */ - $model = new $className; - return '\\' . get_class($model->newCollection()); - } - - /** - * @author Barry vd. Heuvel - * @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 - */ - protected function getReturnTypeFromDocBlock(\ReflectionMethod $reflection) - { - $type = null; - $phpdoc = new DocBlock($reflection); - - if ($phpdoc->hasTag('return')) { - $type = $phpdoc->getTagsByName('return')[0]->getType(); - } - - return $type; - } - protected function getProperty($column): array { $name = $this->option->isCamelCase() ? Str::camel($column['column_name']) : $column['column_name']; @@ -497,4 +338,35 @@ class ModelUpdateVisitor extends NodeVisitorAbstract return $cast; } + + protected function getCollectionClass($className): string + { + // Return something in the very very unlikely scenario the model doesn't + // have a newCollection() method. + if (! method_exists($className, 'newCollection')) { + return Collection::class; + } + + /** @var Model $model */ + $model = new $className(); + return '\\' . get_class($model->newCollection()); + } + + protected static function getReturnFinder(): FindReturnType + { + if (static::$return instanceof FindReturnType) { + return static::$return; + } + + return static::$return = new FindReturnType(); + } + + protected static function getReflector(): ClassReflector + { + if (isset(self::$reflector)) { + return self::$reflector; + } + + return self::$reflector = (new BetterReflection())->classReflector(); + } } From 18306b4224b157422bbf431b7bc6325731a9527b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=93=AD=E6=98=95?= <715557344@qq.com> Date: Mon, 20 Apr 2020 12:43:50 +0800 Subject: [PATCH 4/4] Deleted useless code. --- CHANGELOG.md | 1 + composer.json | 1 - src/database/src/Commands/Ast/ModelUpdateVisitor.php | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caa9a588e..0dbcbdc86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Added +- [#1575](https://github.com/hyperf/hyperf/pull/1575) Added document of property with relation, scope and attributes. - [#1586](https://github.com/hyperf/hyperf/pull/1586) Added conflict of symfony/event-dispatcher which < 4.3. # v1.1.26 - 2020-04-16 diff --git a/composer.json b/composer.json index 9613651f6..6ca77c896 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,6 @@ }, "require-dev": { "bandwidth-throttle/token-bucket": "^2.0", - "barryvdh/reflection-docblock": "^2.0", "doctrine/annotations": "^1.6", "doctrine/common": "@stable", "doctrine/inflector": "^1.3", diff --git a/src/database/src/Commands/Ast/ModelUpdateVisitor.php b/src/database/src/Commands/Ast/ModelUpdateVisitor.php index a1652fbcf..c115e4c35 100644 --- a/src/database/src/Commands/Ast/ModelUpdateVisitor.php +++ b/src/database/src/Commands/Ast/ModelUpdateVisitor.php @@ -363,7 +363,7 @@ class ModelUpdateVisitor extends NodeVisitorAbstract protected static function getReflector(): ClassReflector { - if (isset(self::$reflector)) { + if (self::$reflector instanceof ClassReflector) { return self::$reflector; }