Skip to content

Commit dc2c053

Browse files
authored
Run the collector as a command (#35)
1 parent 957e0ce commit dc2c053

File tree

14 files changed

+713
-559
lines changed

14 files changed

+713
-559
lines changed

CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ None
88

99
### New features
1010

11-
- [#38](https://github.com/olvlvl/composer-attribute-collector/pull/38) Attributes are now collected from interfaces as well as classes.
11+
- [#38](https://github.com/olvlvl/composer-attribute-collector/pull/38) Attributes are now collected from interfaces as well as classes. (@olvlvl)
1212

1313
- [#37](https://github.com/olvlvl/composer-attribute-collector/pull/37) Parameter attributes are now collected. Use the method `findTargetParameters()` to find target parameters, and the method `filterTargetParameters()` to filter target parameters according to a predicate. (@staabm @olvlvl)
1414

15-
- [#39](https://github.com/olvlvl/composer-attribute-collector/pull/39) The `InheritsAttributes` attribute can be used on classes that inherit their attributes from traits, properties, or methods, and were previously ignored by the collection process.
15+
- [#39](https://github.com/olvlvl/composer-attribute-collector/pull/39) The `InheritsAttributes` attribute can be used on classes that inherit their attributes from traits, properties, or methods, and were previously ignored by the collection process. (@olvlvl)
1616

1717
```php
1818
trait UrlTrait
@@ -41,7 +41,9 @@ None
4141

4242
### Other Changes
4343

44-
None
44+
[#35](https://github.com/olvlvl/composer-attribute-collector/pull/35) The collector runs as a
45+
command to avoid clashes between packages used by Composer and those used by the application, such
46+
as incompatible signatures between different versions of the PSR Logger. (@olvlvl)
4547

4648

4749

collector.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
namespace olvlvl\ComposerAttributeCollector;
5+
6+
require 'vendor/autoload.php';
7+
8+
$serializedConfig = $argv[2]
9+
?? throw new \Exception("Configuration is missing");
10+
11+
/** @var Config $config */
12+
$config = unserialize($serializedConfig);
13+
14+
$log = new class($config->isDebug) implements Logger
15+
{
16+
public function __construct(
17+
private bool $isDebug,
18+
) {
19+
}
20+
21+
public function debug(\Stringable|string $message): void
22+
{
23+
if (!$this->isDebug) {
24+
return;
25+
}
26+
27+
fwrite(STDERR, $message . PHP_EOL);
28+
}
29+
30+
public function warning(\Stringable|string $message): void
31+
{
32+
fwrite(STDERR, "\033[33m$message\033[0m" . PHP_EOL);
33+
}
34+
35+
public function error(\Stringable|string $message): void
36+
{
37+
fwrite(STDERR, "\033[31m$message\033[0m" . PHP_EOL);
38+
}
39+
};
40+
41+
$collector = new Collector($config, $log);
42+
$collector->dump();

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
},
3232
"require": {
3333
"php": ">=8.0",
34-
"composer-plugin-api": "^2.0"
34+
"composer-plugin-api": "^2.0",
35+
"composer/class-map-generator": "^1.4"
3536
},
3637
"require-dev": {
3738
"composer/composer": ">=2.4",

src/Collector.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
namespace olvlvl\ComposerAttributeCollector;
4+
5+
use olvlvl\ComposerAttributeCollector\Datastore\FileDatastore;
6+
use olvlvl\ComposerAttributeCollector\Datastore\RuntimeDatastore;
7+
use olvlvl\ComposerAttributeCollector\Filter\ClassFilter;
8+
use olvlvl\ComposerAttributeCollector\Filter\ContentFilter;
9+
use RuntimeException;
10+
11+
/**
12+
* @internal
13+
* @readonly
14+
*/
15+
final class Collector
16+
{
17+
public function __construct(
18+
private Config $config,
19+
private Logger $log,
20+
) {
21+
}
22+
23+
/**
24+
* Dumps the 'attributes.php' file.
25+
*/
26+
public function dump(): void
27+
{
28+
$config = $this->config;
29+
$log = $this->log;
30+
31+
//
32+
// Scan the included paths
33+
//
34+
$start = microtime(true);
35+
$datastore = $this->buildDefaultDatastore();
36+
$classMapGenerator = new MemoizeClassMapGenerator($datastore, $log);
37+
foreach ($config->include as $include) {
38+
$classMapGenerator->scanPaths($include, $config->excludeRegExp);
39+
}
40+
$classMap = $classMapGenerator->getMap();
41+
$elapsed = ElapsedTime::render($start);
42+
$log->debug("Generating attributes file: scanned paths in $elapsed");
43+
44+
//
45+
// Filter the class map
46+
//
47+
$start = microtime(true);
48+
$classMapFilter = new MemoizeClassMapFilter($datastore, $log);
49+
$filter = $this->buildFileFilter();
50+
$classMap = $classMapFilter->filter(
51+
$classMap,
52+
fn (string $class, string $filepath): bool => $filter->filter($filepath, $class, $log)
53+
);
54+
$elapsed = ElapsedTime::render($start);
55+
$log->debug("Generating attributes file: filtered class map in $elapsed");
56+
57+
//
58+
// Collect attributes
59+
//
60+
$start = microtime(true);
61+
$attributeCollector = new MemoizeAttributeCollector(new ClassAttributeCollector($log), $datastore, $log);
62+
$collection = $attributeCollector->collectAttributes($classMap);
63+
$elapsed = ElapsedTime::render($start);
64+
$log->debug("Generating attributes file: collected attributes in $elapsed");
65+
66+
//
67+
// Render attributes
68+
//
69+
$start = microtime(true);
70+
$code = $this->render($collection);
71+
file_put_contents($config->attributesFile, $code);
72+
$elapsed = ElapsedTime::render($start);
73+
$log->debug("Generating attributes file: rendered code in $elapsed");
74+
}
75+
76+
private function buildDefaultDatastore(): Datastore
77+
{
78+
if (!$this->config->useCache) {
79+
return new RuntimeDatastore();
80+
}
81+
82+
$basePath = getcwd() ?: throw new RuntimeException('Unable to locate base path');
83+
84+
return new FileDatastore($basePath . DIRECTORY_SEPARATOR . Plugin::CACHE_DIR, $this->log);
85+
}
86+
87+
private function buildFileFilter(): Filter
88+
{
89+
return new Filter\Chain([
90+
new ContentFilter(),
91+
new ClassFilter()
92+
]);
93+
}
94+
95+
private function render(TransientCollection $collector): string
96+
{
97+
return TransientCollectionRenderer::render($collector);
98+
}
99+
}

src/Config.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ final class Config
3737
*/
3838
public const VENDOR_PLACEHOLDER = '{vendor}';
3939

40-
public static function from(PartialComposer $composer): self
40+
public static function from(PartialComposer $composer, bool $isDebug = false): self
4141
{
4242
$vendorDir = self::resolveVendorDir($composer);
4343
$composerFile = Factory::getComposerFile();
@@ -63,6 +63,7 @@ public static function from(PartialComposer $composer): self
6363
include: $include,
6464
exclude: $exclude,
6565
useCache: $useCache,
66+
isDebug: $isDebug,
6667
);
6768
}
6869

@@ -95,13 +96,16 @@ public static function resolveVendorDir(PartialComposer $composer): string
9596
* Paths that should be excluded from the attribute collection.
9697
* @param bool $useCache
9798
* Whether a cache should be used during the process.
99+
* @param bool $isDebug
100+
* Whether debug messages should be logged.
98101
*/
99102
public function __construct(
100103
public string $vendorDir,
101104
public string $attributesFile,
102105
public array $include,
103106
public array $exclude,
104107
public bool $useCache,
108+
public bool $isDebug,
105109
) {
106110
$this->excludeRegExp = count($exclude) ? self::compileExclude($this->exclude) : null;
107111
}

src/Datastore/RuntimeDatastore.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
use olvlvl\ComposerAttributeCollector\Datastore;
66

7+
/**
8+
* @internal
9+
*/
710
final class RuntimeDatastore implements Datastore
811
{
912
/**

src/ElapsedTime.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace olvlvl\ComposerAttributeCollector;
4+
5+
/**
6+
* @internal
7+
*/
8+
final class ElapsedTime
9+
{
10+
/**
11+
* @param float $start
12+
* Start microtime.
13+
*/
14+
public static function render(float $start): string
15+
{
16+
return sprintf("%.03f ms", (microtime(true) - $start) * 1000);
17+
}
18+
}

src/Logger.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace olvlvl\ComposerAttributeCollector;
44

5+
/**
6+
* @internal
7+
*/
58
interface Logger
69
{
710
public function debug(string|\Stringable $message): void;

src/Logger/ComposerLogger.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
use Composer\IO\IOInterface;
66
use olvlvl\ComposerAttributeCollector\Logger;
77

8+
/**
9+
* @internal
10+
* @readonly
11+
*/
812
final class ComposerLogger implements Logger
913
{
1014
public function __construct(

src/Plugin.php

Lines changed: 9 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,11 @@
77
use Composer\IO\IOInterface;
88
use Composer\Plugin\PluginInterface;
99
use Composer\Script\Event;
10-
use Composer\Util\Platform;
11-
use olvlvl\ComposerAttributeCollector\Datastore\FileDatastore;
12-
use olvlvl\ComposerAttributeCollector\Datastore\RuntimeDatastore;
13-
use olvlvl\ComposerAttributeCollector\Filter\ContentFilter;
14-
use olvlvl\ComposerAttributeCollector\Filter\ClassFilter;
15-
use olvlvl\ComposerAttributeCollector\Logger\ComposerLogger;
10+
use Symfony\Component\Process\Process;
1611

1712
use function file_exists;
1813
use function file_put_contents;
1914
use function microtime;
20-
use function sprintf;
2115

2216
use const DIRECTORY_SEPARATOR;
2317

@@ -82,94 +76,22 @@ public function uninstall(Composer $composer, IOInterface $io): void
8276
public static function onPostAutoloadDump(Event $event): void
8377
{
8478
$composer = $event->getComposer();
85-
$config = Config::from($composer);
8679
$io = $event->getIO();
80+
$config = Config::from($composer, isDebug: $io->isDebug());
8781

8882
require_once $config->vendorDir . "/autoload.php";
8983

90-
$start = microtime(true);
9184
$io->write('<info>Generating attributes file</info>');
92-
$logger = new ComposerLogger($io);
93-
self::dump($config, $logger);
94-
$elapsed = self::renderElapsedTime($start);
95-
$io->write("<info>Generated attributes file in $elapsed</info>");
96-
}
97-
98-
public static function dump(Config $config, Logger $log): void
99-
{
100-
//
101-
// Scan the included paths
102-
//
103-
$start = microtime(true);
104-
$datastore = self::buildDefaultDatastore($config, $log);
105-
$classMapGenerator = new MemoizeClassMapGenerator($datastore, $log);
106-
foreach ($config->include as $include) {
107-
$classMapGenerator->scanPaths($include, $config->excludeRegExp);
108-
}
109-
$classMap = $classMapGenerator->getMap();
110-
$elapsed = self::renderElapsedTime($start);
111-
$log->debug("Generating attributes file: scanned paths in $elapsed");
112-
113-
//
114-
// Filter the class map
115-
//
116-
$start = microtime(true);
117-
$classMapFilter = new MemoizeClassMapFilter($datastore, $log);
118-
$filter = self::buildFileFilter();
119-
$classMap = $classMapFilter->filter(
120-
$classMap,
121-
fn (string $class, string $filepath): bool => $filter->filter($filepath, $class, $log)
122-
);
123-
$elapsed = self::renderElapsedTime($start);
124-
$log->debug("Generating attributes file: filtered class map in $elapsed");
125-
126-
//
127-
// Collect attributes
128-
//
12985
$start = microtime(true);
130-
$attributeCollector = new MemoizeAttributeCollector(new ClassAttributeCollector($log), $datastore, $log);
131-
$collection = $attributeCollector->collectAttributes($classMap);
132-
$elapsed = self::renderElapsedTime($start);
133-
$log->debug("Generating attributes file: collected attributes in $elapsed");
134-
135-
//
136-
// Render attributes
137-
//
138-
$start = microtime(true);
139-
$code = self::render($collection);
140-
file_put_contents($config->attributesFile, $code);
141-
$elapsed = self::renderElapsedTime($start);
142-
$log->debug("Generating attributes file: rendered code in $elapsed");
143-
}
144-
145-
private static function buildDefaultDatastore(Config $config, Logger $log): Datastore
146-
{
147-
if (!$config->useCache) {
148-
return new RuntimeDatastore();
149-
}
150-
151-
$basePath = Platform::getCwd();
152-
153-
assert($basePath !== '');
154-
155-
return new FileDatastore($basePath . DIRECTORY_SEPARATOR . self::CACHE_DIR, $log);
156-
}
157-
158-
private static function renderElapsedTime(float $start): string
159-
{
160-
return sprintf("%.03f ms", (microtime(true) - $start) * 1000);
161-
}
162-
163-
private static function buildFileFilter(): Filter
164-
{
165-
return new Filter\Chain([
166-
new ContentFilter(),
167-
new ClassFilter()
168-
]);
86+
self::dump($config);
87+
$elapsed = ElapsedTime::render($start);
88+
$io->write("<info>Generated attributes file in $elapsed</info>");
16989
}
17090

171-
private static function render(TransientCollection $collector): string
91+
public static function dump(Config $config): void
17292
{
173-
return TransientCollectionRenderer::render($collector);
93+
$cmd = __DIR__ . '/../collector.php';
94+
$process = new Process([ $cmd, '--', serialize($config) ]);
95+
$process->mustRun(fn (string $type, string $line) => print($line));
17496
}
17597
}

0 commit comments

Comments
 (0)