Skip to content

Commit 2300098

Browse files
committed
Adding the Target class
1 parent cd276b6 commit 2300098

5 files changed

Lines changed: 97 additions & 105 deletions

File tree

src/ConsoleTableProcessor.php

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@
44

55
namespace Bakame\Stackwatch;
66

7-
use Closure;
8-
use ReflectionFunctionAbstract;
9-
use ReflectionMethod;
10-
use Symfony\Component\Console\Output\ConsoleOutput;
11-
use Symfony\Component\Console\Output\OutputInterface;
127
use Throwable;
138

149
/**
@@ -20,44 +15,17 @@ public function __construct(public readonly ConsoleTableExporter $exporter)
2015
{
2116
}
2217

23-
public static function fromOutput(OutputInterface $stdout = new ConsoleOutput()): self
24-
{
25-
return new self(new ConsoleTableExporter($stdout));
26-
}
27-
2818
/**
29-
* @param TargetList $targetList
19+
* @param iterable<Target> $targetList
3020
*
3121
* @throws Throwable
3222
*/
3323
public function process(iterable $targetList): void
3424
{
3525
foreach ($targetList as $target) {
36-
/**
37-
* @var Closure $closure
38-
* @var Profile $profile
39-
* @var ReflectionFunctionAbstract $method
40-
*/
41-
['closure' => $closure, 'profile' => $profile, 'method' => $method] = $target;
42-
if (Profile::DETAILED === $profile->type) {
43-
$text = match (true) {
44-
$method instanceof ReflectionMethod => 'Detailed metrics for the method <fg=green>'.$method->class.'::'.$method->getName().'</> located in <fg=green>'.$method->getFileName().'</> called <fg=yellow>'.$profile->iterations.'</> times',
45-
default => 'Detailed metrics for the function <fg=green>'.$method->getName().'</> located in <fg=green>'.$method->getFileName().'</> called <fg=yellow>'.$profile->iterations.'</> times',
46-
};
47-
48-
$this->exporter->output->writeln($text);
49-
$this->exporter->exportReport(Profiler::report($closure, $profile->iterations, $profile->warmup));
50-
51-
continue;
52-
}
53-
54-
$text = match (true) {
55-
$method instanceof ReflectionMethod => 'Average metrics for the method <fg=green>'.$method->class.'::'.$method->getName().'</> located in <fg=green>'.$method->getFileName().'</> called <fg=yellow>'.$profile->iterations.'</> times',
56-
default => 'Average metrics for the function <fg=green>'.$method->getName().'</> located in <fg=green>'.$method->getFileName().'</> called <fg=yellow>'.$profile->iterations.'</> times',
57-
};
58-
59-
$this->exporter->output->writeln($text);
60-
$this->exporter->exportMetrics(Profiler::metrics($closure, $profile->iterations, $profile->warmup));
26+
$this->exporter->output->writeln($target->banner());
27+
$stats = $target->generate();
28+
$stats instanceof Metrics ? $this->exporter->exportMetrics($stats) : $this->exporter->exportReport($stats);
6129
}
6230
}
6331
}

src/JsonProcessor.php

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44

55
namespace Bakame\Stackwatch;
66

7-
use Closure;
8-
use ReflectionFunctionAbstract;
9-
use ReflectionMethod;
10-
use SplFileInfo;
117
use Throwable;
128

139
/**
@@ -20,16 +16,7 @@ public function __construct(public readonly JsonExporter $exporter)
2016
}
2117

2218
/**
23-
* @param SplFileInfo|resource|string $path
24-
* @param ?resource $context
25-
*/
26-
public static function fromStream(mixed $path, int $jsonOptions, $context = null): self
27-
{
28-
return new self(new JsonExporter($path, $jsonOptions, $context));
29-
}
30-
31-
/**
32-
* @param TargetList $targetList
19+
* @param iterable<Target> $targetList
3320
*
3421
* @throws Throwable
3522
*/
@@ -38,26 +25,9 @@ public function process(iterable $targetList): void
3825
$json = [];
3926
$path = null;
4027
foreach ($targetList as $target) {
41-
/**
42-
* @var Closure $closure
43-
* @var Profile $profile
44-
* @var ReflectionFunctionAbstract $method
45-
*/
46-
['closure' => $closure, 'profile' => $profile, 'method' => $method] = $target;
47-
$path ??= $method->getFileName();
48-
$data = [
49-
'type' => $profile->type,
50-
'iterations' => $profile->iterations,
51-
'warmup' => $profile->warmup,
52-
];
53-
54-
if ($method instanceof ReflectionMethod) {
55-
$data['class'] = $method->class;
56-
$data['method'] = $method->getName();
57-
} else {
58-
$data['function'] = $method->getName();
59-
}
60-
$data['attributes'] = Profile::DETAILED === $profile->type ? Profiler::report($closure, $profile->iterations, $profile->warmup) : Profiler::metrics($closure, $profile->iterations, $profile->warmup);
28+
$path ??= $target->source->getFileName();
29+
$data = $target->toArray();
30+
$data['attributes'] = $target->generate();
6131
$json[] = $data;
6232
}
6333

src/PathProfiler.php

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
use Throwable;
2424
use UnitEnum;
2525

26-
use function array_is_list;
2726
use function array_merge;
2827
use function array_reduce;
2928
use function class_exists;
@@ -38,7 +37,6 @@
3837
* using the Profile attribute.
3938
*
4039
* @phpstan-import-type TargetList from Processor
41-
* @phpstan-import-type Target from Processor
4240
*/
4341
final class PathProfiler
4442
{
@@ -109,7 +107,7 @@ public function handleFile(SplFileInfo $path): void
109107
return;
110108
}
111109

112-
$code = $path->openFile('r')->fread($filesize);
110+
$code = $path->openFile()->fread($filesize);
113111
if (false === $code) {
114112
$this->logger->notice('The file '.$realPath.' can not be profiled because it is not readable.', ['path' => $realPath]);
115113

@@ -123,26 +121,25 @@ public function handleFile(SplFileInfo $path): void
123121

124122
require_once $realPath;
125123

126-
/** @var TargetList $targets */
124+
/** @var iterable<Target> $targets */
127125
$targets = array_reduce($tuples, function (array $targets, array $tuple) use ($realPath): array {
128126
$target = match ($tuple[0]) {
129127
'function' => $this->prepareFunctionProcess($tuple[1]),
130128
'class' => $this->prepareMethodsProcess($tuple[1]),
131129
default => throw new LogicException("Unable to prepare process for target type $tuple[1] in $realPath"),
132130
};
133131

134-
if ([] === $target) {
132+
if ([] === $target || null === $target) {
135133
return $targets;
136134
}
137135

138-
if (array_is_list($target)) {
139-
return array_merge($targets, $target);
140-
}
141-
142-
$targets[] = $target;
136+
if ($target instanceof Target) {
137+
$targets[] = $target;
143138

144-
return $targets;
139+
return $targets;
140+
}
145141

142+
return array_merge($targets, $target);
146143
}, []);
147144

148145
$this->processor->process($targets);
@@ -158,19 +155,16 @@ private function findProfile(ReflectionClass|ReflectionFunctionAbstract $ref): ?
158155
return 1 === count($attributes) ? $attributes[0]->newInstance() : null;
159156
}
160157

161-
/**
162-
* @return array{}|Target
163-
*/
164-
public function prepareFunctionProcess(string $functionName): array
158+
public function prepareFunctionProcess(string $functionName): ?Target
165159
{
166160
if (!function_exists($functionName)) {
167-
return [];
161+
return null;
168162
}
169163

170164
$method = new ReflectionFunction($functionName);
171165
$profile = $this->findProfile($method);
172166
if (null === $profile) {
173-
return [];
167+
return null;
174168
}
175169

176170
if (0 !== $method->getNumberOfParameters()) {
@@ -179,20 +173,16 @@ public function prepareFunctionProcess(string $functionName): array
179173
'method' => $functionName,
180174
'path' => $method->getFileName(),
181175
]);
182-
return [];
176+
return null;
183177
}
184178

185-
return [
186-
'closure' => $method->invoke(...),
187-
'profile' => $profile,
188-
'method' => $method,
189-
];
179+
return new Target(callback: $method->invoke(...), profile: $profile, source: $method);
190180
}
191181

192182
/**
193183
* @throws ReflectionException
194184
*
195-
* @return array{}|TargetList
185+
* @return TargetList
196186
*/
197187
public function prepareMethodsProcess(string $className): array
198188
{
@@ -250,12 +240,11 @@ public function prepareMethodsProcess(string $className): array
250240
}
251241
}
252242

253-
$results[] = [
254-
'closure' => fn () => $method->invoke($method->isStatic() ? null : $instance),
255-
'profile' => $profile,
256-
'method' => $method,
257-
];
258-
;
243+
$results[] = new Target(
244+
callback: fn () => $method->invoke($method->isStatic() ? null : $instance),
245+
profile: $profile,
246+
source: $method,
247+
);
259248
}
260249

261250
return $results;

src/Processor.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,11 @@
44

55
namespace Bakame\Stackwatch;
66

7-
use Closure;
8-
use ReflectionFunctionAbstract;
9-
107
/**
11-
* @phpstan-type Target array{closure: Closure, profile: Profile, method: ReflectionFunctionAbstract}
128
* @phpstan-type TargetList list<Target>
139
*/
1410
interface Processor
1511
{
16-
/** @param TargetList $targetList */
12+
/** @param iterable<Target> $targetList */
1713
public function process(iterable $targetList): void;
1814
}

src/Target.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bakame\Stackwatch;
6+
7+
use Closure;
8+
use ReflectionFunctionAbstract;
9+
use ReflectionMethod;
10+
11+
/**
12+
* @phpstan-type TargetMap array{
13+
* type: string,
14+
* iterations: int,
15+
* warmup: int,
16+
* class?: class-string,
17+
* method?: non-empty-string,
18+
* function?: non-empty-string,
19+
* }
20+
*/
21+
final class Target
22+
{
23+
public function __construct(
24+
public readonly Closure $callback,
25+
public readonly Profile $profile,
26+
public readonly ReflectionFunctionAbstract $source,
27+
) {
28+
}
29+
30+
public function generate(): Report|Metrics
31+
{
32+
return Profile::DETAILED === $this->profile->type
33+
? Profiler::report($this->callback, $this->profile->iterations, $this->profile->warmup)
34+
: Profiler::metrics($this->callback, $this->profile->iterations, $this->profile->warmup);
35+
}
36+
37+
/**
38+
* @return TargetMap
39+
*/
40+
public function toArray(): array
41+
{
42+
$data = $this->profile->toArray();
43+
if ($this->source instanceof ReflectionMethod) {
44+
$data['class'] = $this->source->class;
45+
$data['method'] = $this->source->getName();
46+
47+
return $data;
48+
}
49+
50+
$data['function'] = $this->source->getName();
51+
52+
return $data;
53+
}
54+
55+
public function banner(): string
56+
{
57+
if (Profile::DETAILED === $this->profile->type) {
58+
return match (true) {
59+
$this->source instanceof ReflectionMethod => 'Detailed metrics for the method <fg=green>'.$this->source->class.'::'.$this->source->getName().'</> located in <fg=green>'.$this->source->getFileName().'</> called <fg=yellow>'.$this->profile->iterations.'</> times',
60+
default => 'Detailed metrics for the function <fg=green>'.$this->source->getName().'</> located in <fg=green>'.$this->source->getFileName().'</> called <fg=yellow>'.$this->profile->iterations.'</> times',
61+
};
62+
}
63+
64+
return match (true) {
65+
$this->source instanceof ReflectionMethod => 'Average metrics for the method <fg=green>'.$this->source->class.'::'.$this->source->getName().'</> located in <fg=green>'.$this->source->getFileName().'</> called <fg=yellow>'.$this->profile->iterations.'</> times',
66+
default => 'Average metrics for the function <fg=green>'.$this->source->getName().'</> located in <fg=green>'.$this->source->getFileName().'</> called <fg=yellow>'.$this->profile->iterations.'</> times',
67+
};
68+
}
69+
}

0 commit comments

Comments
 (0)