Skip to content

Commit 2c783fe

Browse files
authored
Fix #21042: Separate the @property annotations in case of different types in getters and setters
1 parent 92add55 commit 2c783fe

20 files changed

Lines changed: 183 additions & 72 deletions

File tree

build/controllers/PhpDocController.php

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ protected function fixDocBlockIndentation(&$lines)
411411
}
412412
$docLine = preg_replace('/\s+/', ' ', $docLine);
413413
$docLine = $this->fixParamTypes($docLine);
414-
} elseif (preg_match('/^(~~~|```)/', $docLine)) {
414+
} elseif (strpos($docLine, '```') !== false) {
415415
$codeBlock = !$codeBlock;
416416
$listIndent = '';
417417
} elseif (preg_match('/^(\s*)([0-9]+\.|-|\*|\+) /', $docLine, $matches)) {
@@ -676,7 +676,7 @@ protected function cleanDocComment($doc)
676676
$n = \count($lines);
677677
for ($i = 0; $i < $n; $i++) {
678678
$lines[$i] = rtrim($lines[$i]);
679-
if (trim($lines[$i]) == '*' && trim($lines[$i + 1]) == '*') {
679+
if (trim($lines[$i]) == '*' && isset($lines[$i + 1]) && trim($lines[$i + 1]) == '*') {
680680
unset($lines[$i]);
681681
}
682682
}
@@ -702,10 +702,15 @@ protected function updateDocComment($doc, $properties, $className)
702702
foreach ($lines as $i => $line) {
703703
$line = trim($line);
704704
if (strncmp($line, '* @property', 11) === 0) {
705+
if ($propertyPosition === false) {
706+
$propertyPosition = $i - 1;
707+
}
705708
$propertyPart = true;
706709
} elseif ($propertyPart && $line === '*') {
707710
$propertyPosition = $i;
708711
$propertyPart = false;
712+
} elseif ($propertyPart && $line === '*/') {
713+
$propertyPart = false;
709714
}
710715
if (strncmp($line, '* @author ', 10) === 0 && $propertyPosition === false) {
711716
$propertyPosition = $i - 1;
@@ -833,7 +838,7 @@ protected function generateClassPropertyDocs($fileName)
833838
continue;
834839
}
835840

836-
$acr['comment'] = trim(preg_replace('#(^|\n)\s+\*\s?#', '$1 * ', $acr['comment']));
841+
$acr['comment'] = trim(preg_replace('#(^|\n)\h+\*\h?#', '$1 * ', $acr['comment']));
837842
$props[$acr['name']][$acr['kind']] = [
838843
'type' => $acr['type'],
839844
'comment' => $this->fixSentence($acr['comment']),
@@ -847,33 +852,41 @@ protected function generateClassPropertyDocs($fileName)
847852
ksort($props);
848853

849854
foreach ($props as $propName => &$prop) {
850-
$docLine = ' * @property';
851-
$note = '';
855+
$annotationSuffix = '';
852856
if (isset($prop['get'], $prop['set'])) {
853857
if ($prop['get']['type'] !== $prop['set']['type']) {
854-
$note = ' Note that the type of this property differs in getter and setter.'
855-
. ' See [[get' . ucfirst($propName) . '()]]'
856-
. ' and [[set' . ucfirst($propName) . '()]] for details.';
858+
$phpdoc .= $this->generatePropertyDocLine(
859+
'-read',
860+
$propName,
861+
$prop['get']['type'],
862+
$prop['get']['comment']
863+
);
864+
$phpdoc .= $this->generatePropertyDocLine(
865+
'-write',
866+
$propName,
867+
$prop['set']['type'],
868+
$prop['set']['comment']
869+
);
870+
continue;
857871
}
858872
} elseif (isset($prop['get'])) {
859873
if (!$this->hasSetterInParents($className, $propName)) {
860-
$docLine .= '-read';
874+
$annotationSuffix = '-read';
861875
}
862876
} elseif (isset($prop['set'])) {
863877
if (!$this->hasGetterInParents($className, $propName)) {
864-
$docLine .= '-write';
878+
$annotationSuffix = '-write';
865879
}
866880
} else {
867881
continue;
868882
}
869-
$docLine .= ' ' . $this->getPropParam($prop, 'type') . " $$propName ";
870-
$comment = explode("\n", $this->getPropParam($prop, 'comment') . $note);
871-
foreach ($comment as &$cline) {
872-
$cline = ltrim(rtrim($cline), '* ');
873-
}
874-
$docLine = wordwrap($docLine . implode(' ', $comment), 110, "\n * ") . "\n";
875883

876-
$phpdoc .= $docLine;
884+
$phpdoc .= $this->generatePropertyDocLine(
885+
$annotationSuffix,
886+
$propName,
887+
$this->getPropParam($prop, 'type'),
888+
$this->getPropParam($prop, 'comment')
889+
);
877890
}
878891
}
879892

@@ -918,14 +931,70 @@ protected function fixSentence($str)
918931
return '';
919932
}
920933

921-
return strtoupper(substr($str, 0, 1)) . substr($str, 1) . ($str[\strlen($str) - 1] !== '.' ? '.' : '');
934+
$endsWithCodeFence = substr($str, -3) === '```';
935+
$suffix = !$endsWithCodeFence && $str[\strlen($str) - 1] !== '.' ? '.' : '';
936+
937+
return strtoupper(substr($str, 0, 1)) . substr($str, 1) . $suffix;
922938
}
923939

924940
protected function getPropParam($prop, $param)
925941
{
926942
return isset($prop['property']) ? $prop['property'][$param] : (isset($prop['get']) ? $prop['get'][$param] : $prop['set'][$param]);
927943
}
928944

945+
private function generatePropertyDocLine(
946+
string $annotationSuffix,
947+
string $propName,
948+
string $type,
949+
string $comment
950+
): string {
951+
$docLine = " * @property{$annotationSuffix} {$type} \${$propName} ";
952+
953+
$isExample = false;
954+
$commentLines = [];
955+
$exampleLines = [];
956+
$rawCommentLines = explode("\n", $comment);
957+
958+
foreach ($rawCommentLines as $lineIndex => $line) {
959+
$isCodeFence = strpos($line, '* ```') !== false;
960+
$formattedLine = ltrim(rtrim($line), '* ');
961+
962+
if ($isCodeFence) {
963+
if (!$isExample) {
964+
$isExample = true;
965+
$exampleLines[] = "\n * {$formattedLine}";
966+
continue;
967+
}
968+
969+
$exampleLines[] = " * {$formattedLine}";
970+
$example = implode("\n", $exampleLines);
971+
972+
foreach (array_slice($rawCommentLines, $lineIndex + 1) as $remainingLine) {
973+
if (ltrim(rtrim($remainingLine), '* ') !== '') {
974+
$example .= "\n *";
975+
break;
976+
}
977+
}
978+
979+
$commentLines[] = $example;
980+
$exampleLines = [];
981+
$isExample = false;
982+
continue;
983+
}
984+
985+
if ($isExample) {
986+
$exampleLines[] = rtrim($line);
987+
} elseif ($formattedLine !== '') {
988+
$commentLines[] = $formattedLine;
989+
}
990+
}
991+
992+
$propertyDoc = wordwrap($docLine . implode(' ', $commentLines), 110, "\n * ");
993+
$propertyDoc = preg_replace('/\h+\n/', "\n", $propertyDoc) ?? $propertyDoc;
994+
995+
return $propertyDoc . "\n";
996+
}
997+
929998
/**
930999
* Generate a hash value (message digest)
9311000
* @param string $string message to be hashed.

framework/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Yii Framework 2 Change Log
2828
- Enh #20988, #21040: Add generics to `yii\db\Connection` and `yii\db\Schema` so `getSchema()` and `getQueryBuilder()` infer driver-specific types (terabytesoftw, mspirkov)
2929
- Bug #20994: Fix `@var` annotations for `BlameableBehavior` properties (mspirkov)
3030
- Enh #20990: Show created migration file path on migrate create CLI command (flaviovs)
31+
- Bug #21042: Separate the `@property` annotations in case of different types in getters and setters (mspirkov)
3132

3233

3334
2.0.55 May 09, 2026

framework/base/Model.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@
5555
*
5656
* Empty array if no errors.
5757
* @property-read Validator[] $activeValidators The validators applicable to the current [[scenario]].
58-
* @property array<string, mixed> $attributes Attribute values (name => value). Note that the type of this
59-
* property differs in getter and setter. See [[getAttributes()]] and [[setAttributes()]] for details.
58+
* @property array<string, mixed> $attributes Attribute values (name => value).
6059
* @property-read array<string, string> $firstErrors The first errors. The array keys are the attribute names,
6160
* and the array values are the corresponding error messages. An empty array will be returned if there is no
6261
* error.
@@ -744,7 +743,7 @@ public function getAttributes($names = null, $except = [])
744743

745744
/**
746745
* Sets the attribute values in a massive way.
747-
* @param array $values attribute values (name => value) to be assigned to the model.
746+
* @param array<string, mixed> $values attribute values (name => value) to be assigned to the model.
748747
* @param bool $safeOnly whether the assignments should only be done to the safe attributes.
749748
* A safe attribute is one that is associated with a validation rule in the current [[scenario]].
750749
* @see safeAttributes()

framework/base/Module.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,14 @@
3232
* @property string $layoutPath The root directory of layout files. Defaults to "[[viewPath]]/layouts".
3333
* @property array $modules The modules (indexed by their IDs).
3434
* @property-read string $uniqueId The unique ID of the module.
35-
* @property string $version The version of this module. Note that the type of this property differs in getter
36-
* and setter. See [[getVersion()]] and [[setVersion()]] for details.
35+
* @property-read string $version The version of this module.
36+
* @property-write string|callable|null $version The version of this module. Version can be specified as a PHP
37+
* callback, which can accept module instance as an argument and should return the actual version. For example:
38+
* ```
39+
* function (Module $module) {
40+
* //return string
41+
* }
42+
* ```
3743
* @property string $viewPath The root directory of view files. Defaults to "[[basePath]]/views".
3844
*
3945
* @author Qiang Xue <qiang.xue@gmail.com>

framework/base/Widget.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@
1616
*
1717
* For more details and usage information on Widget, see the [guide article on widgets](guide:structure-widgets).
1818
*
19-
* @property string|null $id ID of the widget. Note that the type of this property differs in getter and
20-
* setter. See [[getId()]] and [[setId()]] for details.
21-
* @property \yii\web\View $view The view object that can be used to render views or view files. Note that the
22-
* type of this property differs in getter and setter. See [[getView()]] and [[setView()]] for details.
19+
* @property-read string|null $id ID of the widget.
20+
* @property-write string $id Id of the widget.
21+
* @property \yii\web\View $view The view object that can be used to render views or view files.
2322
* @property-read string $viewPath The directory containing the view files for this widget.
2423
*
2524
* @author Qiang Xue <qiang.xue@gmail.com>
@@ -191,6 +190,7 @@ public function setId($value)
191190
$this->_id = $value;
192191
}
193192

193+
/** @var \yii\web\View|null */
194194
private $_view;
195195

196196
/**
@@ -203,15 +203,17 @@ public function setId($value)
203203
public function getView()
204204
{
205205
if ($this->_view === null) {
206-
$this->_view = Yii::$app->getView();
206+
/** @var \yii\web\View $view */
207+
$view = Yii::$app->getView();
208+
$this->_view = $view;
207209
}
208210

209211
return $this->_view;
210212
}
211213

212214
/**
213215
* Sets the view object to be used by this widget.
214-
* @param View $view the view object that can be used to render views or view files.
216+
* @param \yii\web\View $view the view object that can be used to render views or view files.
215217
*/
216218
public function setView($view)
217219
{

framework/caching/MemCache.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@
5858
*
5959
* @property-read \Memcache|\Memcached $memcache The memcache (or memcached) object used by this cache
6060
* component.
61-
* @property MemCacheServer[] $servers List of memcache server configurations. Note that the type of this
62-
* property differs in getter and setter. See [[getServers()]] and [[setServers()]] for details.
61+
* @property-read MemCacheServer[] $servers List of memcache server configurations.
62+
* @property-write array $servers List of memcache or memcached server configurations. Each element must be an
63+
* array with the following keys: host, port, persistent, weight, timeout, retryInterval, status.
6364
*
6465
* @author Qiang Xue <qiang.xue@gmail.com>
6566
* @since 2.0

framework/console/controllers/AssetController.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@
4040
* Note: by default this command relies on an external tools to perform actual files compression,
4141
* check [[jsCompressor]] and [[cssCompressor]] for more details.
4242
*
43-
* @property \yii\web\AssetManager $assetManager Asset manager instance. Note that the type of this property
44-
* differs in getter and setter. See [[getAssetManager()]] and [[setAssetManager()]] for details.
43+
* @property-read \yii\web\AssetManager $assetManager Asset manager instance.
44+
* @property-write \yii\web\AssetManager|array $assetManager Asset manager instance or its array
45+
* configuration.
4546
*
4647
* @author Qiang Xue <qiang.xue@gmail.com>
4748
* @author Paul Klimov <klimov.paul@gmail.com>

framework/data/BaseDataProvider.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,17 @@
2121
* @property array $keys The list of key values corresponding to [[models]]. Each data model in [[models]] is
2222
* uniquely identified by the corresponding key value in this array.
2323
* @property array $models The list of data models in the current page.
24-
* @property Pagination|false $pagination The pagination object. If this is false, it means the pagination is
25-
* disabled. Note that the type of this property differs in getter and setter. See [[getPagination()]] and
26-
* [[setPagination()]] for details.
27-
* @property Sort|bool $sort The sorting object. If this is false, it means the sorting is disabled. Note that
28-
* the type of this property differs in getter and setter. See [[getSort()]] and [[setSort()]] for details.
24+
* @property-read Pagination|false $pagination The pagination object. If this is false, it means the
25+
* pagination is disabled.
26+
* @property-write array|Pagination|false $pagination The pagination to be used by this data provider. This
27+
* can be one of the following: - a configuration array for creating the pagination object. The "class" element
28+
* defaults to 'yii\data\Pagination' - an instance of [[Pagination]] or its subclass - false, if pagination needs
29+
* to be disabled.
30+
* @property-read Sort|bool $sort The sorting object. If this is false, it means the sorting is disabled.
31+
* @property-write array|Sort|bool $sort The sort definition to be used by this data provider. This can be one
32+
* of the following: - a configuration array for creating the sort definition object. The "class" element
33+
* defaults to 'yii\data\Sort' - an instance of [[Sort]] or its subclass - false, if sorting needs to be
34+
* disabled.
2935
* @property int $totalCount Total number of possible data models.
3036
*
3137
* @author Qiang Xue <qiang.xue@gmail.com>
@@ -199,7 +205,7 @@ public function getPagination()
199205

200206
/**
201207
* Sets the pagination for this data provider.
202-
* @param array|Pagination|bool $value the pagination to be used by this data provider.
208+
* @param array|Pagination|false $value the pagination to be used by this data provider.
203209
* This can be one of the following:
204210
*
205211
* - a configuration array for creating the pagination object. The "class" element defaults

framework/data/DataFilter.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,14 @@
113113
*
114114
* @see ActiveDataFilter
115115
*
116-
* @property array $errorMessages Error messages in format `[errorKey => message]`. Note that the type of this
117-
* property differs in getter and setter. See [[getErrorMessages()]] and [[setErrorMessages()]] for details.
116+
* @property-read array $errorMessages Error messages in format `[errorKey => message]`.
117+
* @property-write array|\Closure $errorMessages Error messages in `[errorKey => message]` format, or a PHP
118+
* callback returning them.
118119
* @property mixed $filter Raw filter value.
119-
* @property array $searchAttributeTypes Search attribute type map. Note that the type of this property
120-
* differs in getter and setter. See [[getSearchAttributeTypes()]] and [[setSearchAttributeTypes()]] for details.
121-
* @property Model $searchModel Model instance. Note that the type of this property differs in getter and
122-
* setter. See [[getSearchModel()]] and [[setSearchModel()]] for details.
120+
* @property-read array $searchAttributeTypes Search attribute type map.
121+
* @property-write array|null $searchAttributeTypes Search attribute type map.
122+
* @property-read Model $searchModel Model instance.
123+
* @property-write Model|array|string|callable $searchModel Model instance or its DI compatible configuration.
123124
*
124125
* @author Paul Klimov <klimov.paul@gmail.com>
125126
* @since 2.0.13

framework/data/Sort.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,10 @@
6969
*
7070
* For more details and usage information on Sort, see the [guide article on sorting](guide:output-sorting).
7171
*
72-
* @property array $attributeOrders Sort directions indexed by attribute names. Sort direction can be either
73-
* `SORT_ASC` for ascending order or `SORT_DESC` for descending order. Note that the type of this property
74-
* differs in getter and setter. See [[getAttributeOrders()]] and [[setAttributeOrders()]] for details.
72+
* @property-read array $attributeOrders Sort directions indexed by attribute names. Sort direction can be
73+
* either `SORT_ASC` for ascending order or `SORT_DESC` for descending order.
74+
* @property-write array|null $attributeOrders Sort directions indexed by attribute names. Sort direction can
75+
* be either `SORT_ASC` for ascending order or `SORT_DESC` for descending order.
7576
* @property-read array $orders The columns (keys) and their corresponding sort directions (values). This can
7677
* be passed to [[\yii\db\Query::orderBy()]] to construct a DB query.
7778
*

0 commit comments

Comments
 (0)