Skip to content

Commit 8d01535

Browse files
Merge pull request #235 from laravel/de-dupe-eloquent-docblocks
Don't re-document existing Eloquent docblocks
2 parents 327a492 + d2933ff commit 8d01535

File tree

4 files changed

+223
-200
lines changed

4 files changed

+223
-200
lines changed

php-templates/models.php

Lines changed: 108 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,127 @@
11
<?php
22

3-
collect(glob(base_path('**/Models/*.php')))->each(fn ($file) => include_once($file));
3+
collect(glob(base_path('**/Models/*.php')))->each(fn($file) => include_once($file));
44

55
if (class_exists('\phpDocumentor\Reflection\DocBlockFactory')) {
66
$factory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
77
} else {
88
$factory = null;
99
}
1010

11-
$reflection = new \ReflectionClass(\Illuminate\Database\Query\Builder::class);
12-
$builderMethods = collect($reflection->getMethods(\ReflectionMethod::IS_PUBLIC))
13-
->filter(function (ReflectionMethod $method) {
14-
return !str_starts_with($method->getName(), "__");
15-
})
16-
->map(function (\ReflectionMethod $method) use ($factory) {
17-
if ($factory === null) {
18-
$params = collect($method->getParameters())
19-
->map(function (\ReflectionParameter $param) {
20-
$types = match ($param?->getType()) {
21-
null => [],
22-
default => method_exists($param->getType(), "getTypes")
23-
? $param->getType()->getTypes()
24-
: [$param->getType()]
25-
};
26-
27-
$types = collect($types)
28-
->filter()
29-
->values()
30-
->map(fn ($t) => $t->getName());
31-
32-
return trim($types->join("|") . " $" . $param->getName());
33-
})
34-
->all();
35-
36-
$return = $method->getReturnType()?->getName();
37-
} else {
11+
function getMethodDocblocks($method, $factory)
12+
{
13+
if ($factory !== null) {
3814
$docblock = $factory->create($method->getDocComment());
3915
$params = collect($docblock->getTagsByName("param"))->map(fn($p) => (string) $p)->all();
4016
$return = (string) $docblock->getTagsByName("return")[0] ?? null;
17+
18+
return [$params, $return];
19+
}
20+
21+
22+
$params = collect($method->getParameters())
23+
->map(function (\ReflectionParameter $param) {
24+
$types = match ($param?->getType()) {
25+
null => [],
26+
default => method_exists($param->getType(), "getTypes")
27+
? $param->getType()->getTypes()
28+
: [$param->getType()]
29+
};
30+
31+
$types = collect($types)
32+
->filter()
33+
->values()
34+
->map(fn($t) => $t->getName());
35+
36+
return trim($types->join("|") . " $" . $param->getName());
37+
})
38+
->all();
39+
40+
$return = $method->getReturnType()?->getName();
41+
42+
return [$params, $return];
43+
}
44+
45+
function getBuilderMethod($method, $factory)
46+
{
47+
[$params, $return] = getMethodDocblocks($method, $factory);
48+
49+
return [
50+
"name" => $method->getName(),
51+
"parameters" => $params,
52+
"return" => $return,
53+
];
54+
};
55+
56+
function getModelInfo($className, $factory)
57+
{
58+
$output = new \Symfony\Component\Console\Output\BufferedOutput();
59+
60+
try {
61+
\Illuminate\Support\Facades\Artisan::call(
62+
"model:show",
63+
[
64+
"model" => $className,
65+
"--json" => true,
66+
],
67+
$output
68+
);
69+
} catch (\Exception | \Throwable $e) {
70+
return null;
71+
}
72+
73+
$data = json_decode($output->fetch(), true);
74+
75+
if ($data === null) {
76+
return null;
77+
}
78+
79+
$reflection = (new \ReflectionClass($className));
80+
81+
if ($factory !== null && ($comment = $reflection->getDocComment())) {
82+
$docblock = $factory->create($comment);
83+
$existingProperties = collect($docblock->getTagsByName("property"))->map(fn($p) => $p->getVariableName());
84+
$existingReadProperties = collect($docblock->getTagsByName("property-read"))->map(fn($p) => $p->getVariableName());
85+
$existingProperties = $existingProperties->merge($existingReadProperties);
86+
} else {
87+
$existingProperties = collect();
4188
}
4289

90+
$data['attributes'] = collect($data['attributes'])
91+
->map(fn($attrs) => array_merge($attrs, [
92+
'title_case' => str_replace('_', '', \Illuminate\Support\Str::title($attrs['name'])),
93+
'documented' => $existingProperties->contains($attrs['name']),
94+
]))
95+
->toArray();
96+
97+
$data['scopes'] = collect($reflection->getMethods())
98+
->filter(fn($method) => $method->isPublic() && !$method->isStatic() && $method->name !== '__construct')
99+
->filter(fn($method) => str_starts_with($method->name, 'scope'))
100+
->map(fn($method) => str_replace('scope', '', $method->name))
101+
->map(fn($method) => strtolower(substr($method, 0, 1)) . substr($method, 1))
102+
->values()
103+
->toArray();
104+
105+
$data['uri'] = $reflection->getFileName();
106+
43107
return [
44-
"name" => $method->getName(),
45-
"parameters" => $params,
46-
"return" => $return,
108+
$className => $data,
47109
];
48-
})
49-
->filter()
50-
->values();
110+
}
111+
112+
$reflection = new \ReflectionClass(\Illuminate\Database\Query\Builder::class);
113+
$builderMethods = collect($reflection->getMethods(\ReflectionMethod::IS_PUBLIC))
114+
->filter(fn(ReflectionMethod $method) => !str_starts_with($method->getName(), "__"))
115+
->map(fn(\ReflectionMethod $method) => getBuilderMethod($method, $factory))
116+
->filter()
117+
->values();
51118

52119
echo collect([
53-
'builderMethods' => $builderMethods,
54-
'models' => collect(get_declared_classes())
55-
->filter(function ($class) {
56-
return is_subclass_of($class, \Illuminate\Database\Eloquent\Model::class);
57-
})
58-
->filter(function ($class) {
59-
return !in_array($class, [\Illuminate\Database\Eloquent\Relations\Pivot::class, \Illuminate\Foundation\Auth\User::class]);
60-
})
61-
->values()
62-
->flatMap(function (string $className) {
63-
$output = new \Symfony\Component\Console\Output\BufferedOutput();
64-
65-
try {
66-
\Illuminate\Support\Facades\Artisan::call(
67-
"model:show",
68-
[
69-
"model" => $className,
70-
"--json" => true,
71-
],
72-
$output
73-
);
74-
} catch (\Exception | \Throwable $e) {
75-
return null;
76-
}
77-
78-
$data = json_decode($output->fetch(), true);
79-
80-
if ($data === null) {
81-
return null;
82-
}
83-
84-
$data['attributes'] = collect($data['attributes'])
85-
->map(function ($attrs) {
86-
return array_merge($attrs, [
87-
'title_case' => str_replace('_', '', \Illuminate\Support\Str::title($attrs['name'])),
88-
]);
89-
})
90-
->toArray();
91-
92-
$reflection = (new \ReflectionClass($className));
93-
94-
$data['scopes'] = collect($reflection->getMethods())
95-
->filter(function ($method) {
96-
return $method->isPublic() && !$method->isStatic() && $method->name !== '__construct';
97-
})
98-
->filter(function ($method) {
99-
return str_starts_with($method->name, 'scope');
100-
})
101-
->map(function ($method) {
102-
return str_replace('scope', '', $method->name);
103-
})
104-
->map(function ($method) {
105-
return strtolower(substr($method, 0, 1)) . substr($method, 1);
106-
})
107-
->values()
108-
->toArray();
109-
110-
$data['uri'] = $reflection->getFileName();
111-
112-
return [
113-
$className => $data,
114-
];
115-
})
116-
->filter(),
120+
'builderMethods' => $builderMethods,
121+
'models' => collect(get_declared_classes())
122+
->filter(fn($class) => is_subclass_of($class, \Illuminate\Database\Eloquent\Model::class))
123+
->filter(fn($class) => !in_array($class, [\Illuminate\Database\Eloquent\Relations\Pivot::class, \Illuminate\Foundation\Auth\User::class]))
124+
->values()
125+
->flatMap(fn(string $className) => getModelInfo($className, $factory))
126+
->filter(),
117127
])->toJson();

src/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ declare namespace Eloquent {
9696
appended: null;
9797
cast: string | null;
9898
title_case: string;
99+
documented: boolean;
99100
}
100101

101102
interface Relation {

src/support/docblocks.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,15 +177,17 @@ const getAttributeBlocks = (
177177
attr: Eloquent.Attribute,
178178
className: string,
179179
): string[] => {
180-
const blocks = [];
180+
const blocks: string[] = [];
181181

182182
const propType = ["accessor", "attribute"].includes(attr.cast || "")
183183
? "@property-read"
184184
: "@property";
185185

186-
const type = getAttributeType(attr);
186+
if (!attr.documented) {
187+
const type = getAttributeType(attr);
187188

188-
blocks.push(`${propType} ${type} $${attr.name}`);
189+
blocks.push(`${propType} ${type} $${attr.name}`);
190+
}
189191

190192
if (!["accessor", "attribute"].includes(attr.cast || "")) {
191193
blocks.push(
@@ -220,7 +222,7 @@ const mapType = (type: string): string => {
220222
/char\(\d+\)/,
221223
],
222224
float: [/double\(\d+\,\d+\)/],
223-
int: ["bigint", "bigint unsigned", "integer"],
225+
int: ["bigint", "bigint unsigned", "integer", "int unsigned"],
224226
mixed: ["attribute", "accessor", "encrypted"],
225227
array: ["encrypted:json", "encrypted:array", "json"],
226228
"\\Illuminate\\Support\\Carbon": ["datetime", "timestamp"],

0 commit comments

Comments
 (0)