From a8dc9dbff9fba619e28ad6320978cf0d50395c79 Mon Sep 17 00:00:00 2001 From: Pavel Date: Mon, 13 May 2024 15:30:44 +0300 Subject: [PATCH 1/4] added ActiveRecord extension type to support findBySql method usages --- .gitignore | 1 + extension.neon | 3 + ...actActiveRecordFindReturnTypeExtension.php | 79 +++++++++++++++++++ ...tiveRecordFindBySqlReturnTypeExtension.php | 16 ++++ .../ActiveRecordFindReturnTypeExtension.php | 67 +--------------- ...RecordFindBySqlReturnTypeExtensionTest.php | 31 ++++++++ .../active-query-builder-return-type.php | 29 +++++++ .../active-record-find-by-sql-return-type.php | 23 ++++++ .../Type/_data/active-record-object-type.php | 6 ++ tests/Yii/Comment.php | 8 ++ 10 files changed, 199 insertions(+), 64 deletions(-) create mode 100644 src/Type/AbstractActiveRecordFindReturnTypeExtension.php create mode 100644 src/Type/ActiveRecordFindBySqlReturnTypeExtension.php create mode 100644 tests/Type/ActiveRecordFindBySqlReturnTypeExtensionTest.php create mode 100644 tests/Type/_data/active-record-find-by-sql-return-type.php diff --git a/.gitignore b/.gitignore index 0939e3c..94b9840 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /.phpunit.result.cache /phpunit.xml /phpstan.neon +/.idea diff --git a/extension.neon b/extension.neon index 8d62280..8086978 100644 --- a/extension.neon +++ b/extension.neon @@ -93,6 +93,9 @@ services: - class: ErickSkrauch\PHPStan\Yii2\Type\ActiveRecordFindReturnTypeExtension tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension] + - class: ErickSkrauch\PHPStan\Yii2\Type\ActiveRecordFindBySqlReturnTypeExtension + tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension] + - class: ErickSkrauch\PHPStan\Yii2\Type\ActiveRecordRelationGetterReturnTypeExtension tags: [phpstan.broker.dynamicMethodReturnTypeExtension] diff --git a/src/Type/AbstractActiveRecordFindReturnTypeExtension.php b/src/Type/AbstractActiveRecordFindReturnTypeExtension.php new file mode 100644 index 0000000..3183157 --- /dev/null +++ b/src/Type/AbstractActiveRecordFindReturnTypeExtension.php @@ -0,0 +1,79 @@ +reflectionProvider = $reflectionProvider; + } + + public function getClass(): string { + return ActiveRecordInterface::class; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection): bool { + return $methodReflection->getName() === static::methodName(); + } + + public function getTypeFromStaticMethodCall( + MethodReflection $methodReflection, + StaticCall $methodCall, + Scope $scope + ): ?Type { + $calledOn = $methodCall->class; + if ($calledOn instanceof Name) { + return $this->createType($scope->resolveName($calledOn), $scope); + } + + $types = []; + if ($calledOn instanceof Variable) { + foreach ($scope->getType($calledOn)->getConstantStrings() as $constantString) { + if (!$constantString->isClassStringType()->yes()) { + return new NeverType(); + } + + $types[] = $this->createType($constantString->getValue(), $scope); + } + + return TypeCombinator::union(...$types); + } + + return null; + } + + abstract protected function methodName(): string; + + private function createType(string $modelClass, Scope $scope): Type { + $method = $this->reflectionProvider->getClass($modelClass)->getMethod(static::methodName(), $scope); + $returnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType(); + if (!$returnType->isObject()->yes()) { + throw new ShouldNotHappenException(); + } + + $types = []; + foreach ($returnType->getObjectClassNames() as $className) { + $types[] = new ActiveQueryObjectType(new ActiveRecordObjectType($modelClass), $className); + } + + return TypeCombinator::union(...$types); + } + +} diff --git a/src/Type/ActiveRecordFindBySqlReturnTypeExtension.php b/src/Type/ActiveRecordFindBySqlReturnTypeExtension.php new file mode 100644 index 0000000..563e604 --- /dev/null +++ b/src/Type/ActiveRecordFindBySqlReturnTypeExtension.php @@ -0,0 +1,16 @@ +reflectionProvider = $reflectionProvider; - } - - public function getClass(): string { - return ActiveRecordInterface::class; - } - - public function isStaticMethodSupported(MethodReflection $methodReflection): bool { - return $methodReflection->getName() === 'find'; - } - - public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - $calledOn = $methodCall->class; - if ($calledOn instanceof Name) { - return $this->createType($scope->resolveName($calledOn), $scope); - } - - $types = []; - if ($calledOn instanceof Variable) { - foreach ($scope->getType($calledOn)->getConstantStrings() as $constantString) { - if (!$constantString->isClassStringType()->yes()) { - return new NeverType(); - } - - $types[] = $this->createType($constantString->getValue(), $scope); - } - - return TypeCombinator::union(...$types); - } - - return null; - } - - private function createType(string $modelClass, Scope $scope): Type { - $method = $this->reflectionProvider->getClass($modelClass)->getMethod('find', $scope); - $returnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType(); - if (!$returnType->isObject()->yes()) { - throw new ShouldNotHappenException(); - } - - $types = []; - foreach ($returnType->getObjectClassNames() as $className) { - $types[] = new ActiveQueryObjectType(new ActiveRecordObjectType($modelClass), $className); - } +final class ActiveRecordFindReturnTypeExtension extends AbstractActiveRecordFindReturnTypeExtension { - return TypeCombinator::union(...$types); + protected function methodName(): string { + return 'find'; } } diff --git a/tests/Type/ActiveRecordFindBySqlReturnTypeExtensionTest.php b/tests/Type/ActiveRecordFindBySqlReturnTypeExtensionTest.php new file mode 100644 index 0000000..85c664d --- /dev/null +++ b/tests/Type/ActiveRecordFindBySqlReturnTypeExtensionTest.php @@ -0,0 +1,31 @@ + + */ + public static function dataFileAsserts(): iterable { + yield from self::gatherAssertTypes(__DIR__ . '/_data/active-record-find-by-sql-return-type.php'); + } + + /** + * @param mixed $args + * @dataProvider dataFileAsserts + */ + public function testFileAsserts(string $assertType, string $file, ...$args): void { + $this->assertFileAsserts($assertType, $file, ...$args); + } + +} diff --git a/tests/Type/_data/active-query-builder-return-type.php b/tests/Type/_data/active-query-builder-return-type.php index 00dcb15..d1137df 100644 --- a/tests/Type/_data/active-query-builder-return-type.php +++ b/tests/Type/_data/active-query-builder-return-type.php @@ -10,18 +10,30 @@ assertType(Article::class . '|null', Article::find()->one()); assertType('array', Article::find()->all()); +assertType(Article::class . '|null', Article::findBySql('')->one()); +assertType('array', Article::findBySql('')->all()); + // Preserve when built-in filtering assertType('array', Comment::find()->andWhere(['user_id' => 123])->all()); +assertType('array', Comment::findBySql('')->andWhere(['user_id' => 123])->all()); + // Preserve when custom filter assertType('array', Comment::find()->notDeletedSelf()->all()); assertType('array', Comment::find()->notDeletedStatic()->all()); assertType('array', Comment::find()->notDeletedThis()->all()); +assertType('array', Comment::findBySql('')->notDeletedSelf()->all()); +assertType('array', Comment::findBySql('')->notDeletedStatic()->all()); +assertType('array', Comment::findBySql('')->notDeletedThis()->all()); + // As array assertType('array|null', Comment::find()->asArray()->one()); assertType('array>', Comment::find()->asArray()->all()); +assertType('array|null', Comment::findBySql('')->asArray()->one()); +assertType('array>', Comment::findBySql('')->asArray()->all()); + // Index by assertType('array', Comment::find()->indexBy('user_id')->all()); assertType('array', Comment::find()->indexBy(fn() => 'key')->all()); @@ -30,14 +42,31 @@ assertType('array', Comment::find()->indexBy(null)->all()); assertType('array>', Comment::find()->asArray()->indexBy(null)->all()); +assertType('array', Comment::findBySql('')->indexBy('user_id')->all()); +assertType('array', Comment::findBySql('')->indexBy(fn() => 'key')->all()); +assertType('array>', Comment::findBySql('')->asArray()->indexBy('user_id')->all()); +assertType('array>', Comment::findBySql('')->asArray()->indexBy(fn() => 'key')->all()); +assertType('array', Comment::findBySql('')->indexBy(null)->all()); +assertType('array>', Comment::findBySql('')->asArray()->indexBy(null)->all()); + // Batch assertType(BatchQueryResult::class . '>', Comment::find()->batch(250)); assertType(BatchQueryResult::class . '>>', Comment::find()->asArray()->batch(250)); assertType(BatchQueryResult::class . '>', Comment::find()->indexBy('user_id')->batch(250)); assertType(BatchQueryResult::class . '>>', Comment::find()->asArray()->indexBy('user_id')->batch(250)); +assertType(BatchQueryResult::class . '>', Comment::findBySql('')->batch(250)); +assertType(BatchQueryResult::class . '>>', Comment::findBySql('')->asArray()->batch(250)); +assertType(BatchQueryResult::class . '>', Comment::findBySql('')->indexBy('user_id')->batch(250)); +assertType(BatchQueryResult::class . '>>', Comment::findBySql('')->asArray()->indexBy('user_id')->batch(250)); + // Each assertType(BatchQueryResult::class . '', Comment::find()->each(250)); assertType(BatchQueryResult::class . '>', Comment::find()->asArray()->each(250)); assertType(BatchQueryResult::class . '', Comment::find()->indexBy('user_id')->each(250)); assertType(BatchQueryResult::class . '>', Comment::find()->asArray()->indexBy('user_id')->each(250)); + +assertType(BatchQueryResult::class . '', Comment::findBySql('')->each(250)); +assertType(BatchQueryResult::class . '>', Comment::findBySql('')->asArray()->each(250)); +assertType(BatchQueryResult::class . '', Comment::findBySql('')->indexBy('user_id')->each(250)); +assertType(BatchQueryResult::class . '>', Comment::findBySql('')->asArray()->indexBy('user_id')->each(250)); diff --git a/tests/Type/_data/active-record-find-by-sql-return-type.php b/tests/Type/_data/active-record-find-by-sql-return-type.php new file mode 100644 index 0000000..6aa6731 --- /dev/null +++ b/tests/Type/_data/active-record-find-by-sql-return-type.php @@ -0,0 +1,23 @@ +', Article::findBySql('')); +assertType(CommentsQuery::class . '<' . Comment::class . '>', Comment::findBySql('')); + +$class = Article::class; +assertType(ActiveQuery::class . '<' . Article::class . '>', $class::findBySql('')); + +if (random_int(0, 10) === 0) { + $class = Comment::class; +} + +assertType( + CommentsQuery::class . '<' . Comment::class . '>|' . ActiveQuery::class . '<' . Article::class . '>', + $class::findBySql(''), +); diff --git a/tests/Type/_data/active-record-object-type.php b/tests/Type/_data/active-record-object-type.php index eababb1..deb3324 100644 --- a/tests/Type/_data/active-record-object-type.php +++ b/tests/Type/_data/active-record-object-type.php @@ -8,10 +8,16 @@ assertType('bool', isset(Article::find()->one()['id'])); assertType('bool', isset(Article::find()->one()['text'])); +assertType('bool', isset(Article::findBySql('')->one()['id'])); +assertType('bool', isset(Article::findBySql('')->one()['text'])); + // Read assertType('int', Article::find()->one()['id']); assertType('string', Article::find()->one()['text']); +assertType('int', Article::findBySql('')->one()['id']); +assertType('string', Article::findBySql('')->one()['text']); + // Write $article = Article::find()->one(); $article['id'] = 123; diff --git a/tests/Yii/Comment.php b/tests/Yii/Comment.php index 903850b..9e5c677 100644 --- a/tests/Yii/Comment.php +++ b/tests/Yii/Comment.php @@ -18,6 +18,14 @@ public static function find(): CommentsQuery { return new CommentsQuery(self::class); } + /** + * @param string $sql + * @param array $params + */ + public static function findBySql($sql, $params = []): CommentsQuery { + return new CommentsQuery(self::class); + } + public static function findById(string $id): ?self { return self::find()->notDeletedSelf()->notDeletedStatic()->notDeletedStatic()->limit(1)->one(); } From 1a12648a083a0d4da60d19e0717235b9086f47e2 Mon Sep 17 00:00:00 2001 From: Pavel Naumov Date: Fri, 24 May 2024 11:03:10 +0300 Subject: [PATCH 2/4] deleted user specific config from project .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 94b9840..0939e3c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ /.phpunit.result.cache /phpunit.xml /phpstan.neon -/.idea From 7d37437c7dd1e9506807a1752db033c972580bf2 Mon Sep 17 00:00:00 2001 From: Pavel Naumov Date: Mon, 27 May 2024 16:42:45 +0300 Subject: [PATCH 3/4] made findBySql inherit types from find --- .../AbstractActiveRecordFindReturnTypeExtension.php | 2 +- tests/Yii/Comment.php | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Type/AbstractActiveRecordFindReturnTypeExtension.php b/src/Type/AbstractActiveRecordFindReturnTypeExtension.php index 3183157..a735136 100644 --- a/src/Type/AbstractActiveRecordFindReturnTypeExtension.php +++ b/src/Type/AbstractActiveRecordFindReturnTypeExtension.php @@ -62,7 +62,7 @@ public function getTypeFromStaticMethodCall( abstract protected function methodName(): string; private function createType(string $modelClass, Scope $scope): Type { - $method = $this->reflectionProvider->getClass($modelClass)->getMethod(static::methodName(), $scope); + $method = $this->reflectionProvider->getClass($modelClass)->getMethod('find', $scope); $returnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType(); if (!$returnType->isObject()->yes()) { throw new ShouldNotHappenException(); diff --git a/tests/Yii/Comment.php b/tests/Yii/Comment.php index 9e5c677..da1b983 100644 --- a/tests/Yii/Comment.php +++ b/tests/Yii/Comment.php @@ -7,8 +7,8 @@ use yii\db\ActiveRecord; /** - * @property int $id - * @property int $article_id + * @property int $id + * @property int $article_id * @property string $text * @property string $field */ @@ -18,14 +18,6 @@ public static function find(): CommentsQuery { return new CommentsQuery(self::class); } - /** - * @param string $sql - * @param array $params - */ - public static function findBySql($sql, $params = []): CommentsQuery { - return new CommentsQuery(self::class); - } - public static function findById(string $id): ?self { return self::find()->notDeletedSelf()->notDeletedStatic()->notDeletedStatic()->limit(1)->one(); } From c5539958611ca7fdc6d648b85359ae4f87a03417 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 28 May 2024 21:21:29 +0200 Subject: [PATCH 4/4] Rework implementation --- extension.neon | 3 - ...actActiveRecordFindReturnTypeExtension.php | 79 ------------------ ...tiveRecordFindBySqlReturnTypeExtension.php | 16 ---- .../ActiveRecordFindReturnTypeExtension.php | 82 ++++++++++++++++++- ...RecordFindBySqlReturnTypeExtensionTest.php | 31 ------- .../active-record-find-by-sql-return-type.php | 23 ------ .../_data/active-record-find-return-type.php | 12 ++- tests/Yii/Comment.php | 4 +- 8 files changed, 92 insertions(+), 158 deletions(-) delete mode 100644 src/Type/AbstractActiveRecordFindReturnTypeExtension.php delete mode 100644 src/Type/ActiveRecordFindBySqlReturnTypeExtension.php delete mode 100644 tests/Type/ActiveRecordFindBySqlReturnTypeExtensionTest.php delete mode 100644 tests/Type/_data/active-record-find-by-sql-return-type.php diff --git a/extension.neon b/extension.neon index 8086978..8d62280 100644 --- a/extension.neon +++ b/extension.neon @@ -93,9 +93,6 @@ services: - class: ErickSkrauch\PHPStan\Yii2\Type\ActiveRecordFindReturnTypeExtension tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension] - - class: ErickSkrauch\PHPStan\Yii2\Type\ActiveRecordFindBySqlReturnTypeExtension - tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension] - - class: ErickSkrauch\PHPStan\Yii2\Type\ActiveRecordRelationGetterReturnTypeExtension tags: [phpstan.broker.dynamicMethodReturnTypeExtension] diff --git a/src/Type/AbstractActiveRecordFindReturnTypeExtension.php b/src/Type/AbstractActiveRecordFindReturnTypeExtension.php deleted file mode 100644 index a735136..0000000 --- a/src/Type/AbstractActiveRecordFindReturnTypeExtension.php +++ /dev/null @@ -1,79 +0,0 @@ -reflectionProvider = $reflectionProvider; - } - - public function getClass(): string { - return ActiveRecordInterface::class; - } - - public function isStaticMethodSupported(MethodReflection $methodReflection): bool { - return $methodReflection->getName() === static::methodName(); - } - - public function getTypeFromStaticMethodCall( - MethodReflection $methodReflection, - StaticCall $methodCall, - Scope $scope - ): ?Type { - $calledOn = $methodCall->class; - if ($calledOn instanceof Name) { - return $this->createType($scope->resolveName($calledOn), $scope); - } - - $types = []; - if ($calledOn instanceof Variable) { - foreach ($scope->getType($calledOn)->getConstantStrings() as $constantString) { - if (!$constantString->isClassStringType()->yes()) { - return new NeverType(); - } - - $types[] = $this->createType($constantString->getValue(), $scope); - } - - return TypeCombinator::union(...$types); - } - - return null; - } - - abstract protected function methodName(): string; - - private function createType(string $modelClass, Scope $scope): Type { - $method = $this->reflectionProvider->getClass($modelClass)->getMethod('find', $scope); - $returnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType(); - if (!$returnType->isObject()->yes()) { - throw new ShouldNotHappenException(); - } - - $types = []; - foreach ($returnType->getObjectClassNames() as $className) { - $types[] = new ActiveQueryObjectType(new ActiveRecordObjectType($modelClass), $className); - } - - return TypeCombinator::union(...$types); - } - -} diff --git a/src/Type/ActiveRecordFindBySqlReturnTypeExtension.php b/src/Type/ActiveRecordFindBySqlReturnTypeExtension.php deleted file mode 100644 index 563e604..0000000 --- a/src/Type/ActiveRecordFindBySqlReturnTypeExtension.php +++ /dev/null @@ -1,16 +0,0 @@ -reflectionProvider = $reflectionProvider; + } + + public function getClass(): string { + return ActiveRecordInterface::class; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection): bool { + return in_array($methodReflection->getName(), ['find', 'findBySql', 'findByCondition'], true); + } + + public function getTypeFromStaticMethodCall( + MethodReflection $methodReflection, + StaticCall $methodCall, + Scope $scope + ): ?Type { + $calledOn = $methodCall->class; + $declaringClass = $methodReflection->getDeclaringClass(); + // The implementations of the ::findBySql() and ::findByCondition() methods rely on the ::find() method, + // so unless they have been overridden, we return the ::find() method type + if ($methodReflection->getName() !== 'find' && $declaringClass->getName() === ActiveRecord::class) { + $findMethod = $declaringClass->getMethod('find', $scope); + $findCall = new StaticCall($calledOn, 'find'); // According to the Yii2 implementation, this call will have no arguments + + return $this->getTypeFromStaticMethodCall($findMethod, $findCall, $scope); + } + + if ($calledOn instanceof Name) { + return $this->createType($scope->resolveName($calledOn), $methodReflection->getName(), $scope); + } + + $types = []; + if ($calledOn instanceof Variable) { + foreach ($scope->getType($calledOn)->getConstantStrings() as $constantString) { + if (!$constantString->isClassStringType()->yes()) { + return new NeverType(); + } + + $types[] = $this->createType($constantString->getValue(), $methodReflection->getName(), $scope); + } + + return TypeCombinator::union(...$types); + } + + return null; + } + + private function createType(string $modelClass, string $methodName, Scope $scope): Type { + $method = $this->reflectionProvider->getClass($modelClass)->getMethod($methodName, $scope); + $returnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType(); + if (!$returnType->isObject()->yes()) { + throw new ShouldNotHappenException(); + } + + $types = []; + foreach ($returnType->getObjectClassNames() as $className) { + $types[] = new ActiveQueryObjectType(new ActiveRecordObjectType($modelClass), $className); + } - protected function methodName(): string { - return 'find'; + return TypeCombinator::union(...$types); } } diff --git a/tests/Type/ActiveRecordFindBySqlReturnTypeExtensionTest.php b/tests/Type/ActiveRecordFindBySqlReturnTypeExtensionTest.php deleted file mode 100644 index 85c664d..0000000 --- a/tests/Type/ActiveRecordFindBySqlReturnTypeExtensionTest.php +++ /dev/null @@ -1,31 +0,0 @@ - - */ - public static function dataFileAsserts(): iterable { - yield from self::gatherAssertTypes(__DIR__ . '/_data/active-record-find-by-sql-return-type.php'); - } - - /** - * @param mixed $args - * @dataProvider dataFileAsserts - */ - public function testFileAsserts(string $assertType, string $file, ...$args): void { - $this->assertFileAsserts($assertType, $file, ...$args); - } - -} diff --git a/tests/Type/_data/active-record-find-by-sql-return-type.php b/tests/Type/_data/active-record-find-by-sql-return-type.php deleted file mode 100644 index 6aa6731..0000000 --- a/tests/Type/_data/active-record-find-by-sql-return-type.php +++ /dev/null @@ -1,23 +0,0 @@ -', Article::findBySql('')); -assertType(CommentsQuery::class . '<' . Comment::class . '>', Comment::findBySql('')); - -$class = Article::class; -assertType(ActiveQuery::class . '<' . Article::class . '>', $class::findBySql('')); - -if (random_int(0, 10) === 0) { - $class = Comment::class; -} - -assertType( - CommentsQuery::class . '<' . Comment::class . '>|' . ActiveQuery::class . '<' . Article::class . '>', - $class::findBySql(''), -); diff --git a/tests/Type/_data/active-record-find-return-type.php b/tests/Type/_data/active-record-find-return-type.php index b0fba6f..f166136 100644 --- a/tests/Type/_data/active-record-find-return-type.php +++ b/tests/Type/_data/active-record-find-return-type.php @@ -8,13 +8,23 @@ use function PHPStan\Testing\assertType; assertType(ActiveQuery::class . '<' . Article::class . '>', Article::find()); +assertType(ActiveQuery::class . '<' . Article::class . '>', Article::findBySql('')); assertType(CommentsQuery::class . '<' . Comment::class . '>', Comment::find()); +assertType(CommentsQuery::class . '<' . Comment::class . '>', Comment::findBySql('')); $class = Article::class; assertType(ActiveQuery::class . '<' . Article::class . '>', $class::find()); +assertType(ActiveQuery::class . '<' . Article::class . '>', $class::findBySql('')); if (random_int(0, 10) === 0) { $class = Comment::class; } -assertType(CommentsQuery::class . '<' . Comment::class . '>|' . ActiveQuery::class . '<' . Article::class . '>', $class::find()); +assertType( + CommentsQuery::class . '<' . Comment::class . '>|' . ActiveQuery::class . '<' . Article::class . '>', + $class::find(), +); +assertType( + CommentsQuery::class . '<' . Comment::class . '>|' . ActiveQuery::class . '<' . Article::class . '>', + $class::findBySql(''), +); diff --git a/tests/Yii/Comment.php b/tests/Yii/Comment.php index da1b983..903850b 100644 --- a/tests/Yii/Comment.php +++ b/tests/Yii/Comment.php @@ -7,8 +7,8 @@ use yii\db\ActiveRecord; /** - * @property int $id - * @property int $article_id + * @property int $id + * @property int $article_id * @property string $text * @property string $field */