Skip to content

Commit a790af3

Browse files
committed
Add search command support
1 parent d113b26 commit a790af3

File tree

5 files changed

+235
-3
lines changed

5 files changed

+235
-3
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@
2222
"coverage": "XDEBUG_MODE=coverage phpunit --testdox --colors=\"auto\" --coverage-html=\"coverage\""
2323
},
2424
"require": {
25-
"php": "^7.4.0 || ^8.0 || ^8.1",
25+
"php": "^7.4.0 || ^8.0",
2626
"ext-openssl": "*",
2727
"ext-zip": "*",
2828
"ext-zlib": "*",
2929
"composer/composer": "^2.1.0",
3030
"symfony/process": "^4.3.4 || ^5.0 || ^6.0"
3131
},
3232
"require-dev": {
33+
"dms/phpunit-arraysubset-asserts": "^0.4.0",
3334
"phpstan/phpstan": "^1.6",
3435
"phpunit/phpunit": "^8.5.15 || ^9.5.4",
3536
"symfony/phpunit-bridge": "^4.3.4 || ^5.0 || ^6.0"

src/Commands/Search.php

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
3+
namespace Winter\Packager\Commands;
4+
5+
use Winter\Packager\Exceptions\CommandException;
6+
7+
/**
8+
* Search command.
9+
*
10+
* Runs "composer search" within PHP.
11+
*
12+
* @author Ben Thomson
13+
* @since 0.2.0
14+
*/
15+
class Search extends BaseCommand
16+
{
17+
/**
18+
* The search query to find packages.
19+
*/
20+
public string $query;
21+
22+
/**
23+
* The type of package to search for.
24+
*/
25+
public ?string $type = null;
26+
27+
/**
28+
* Limit the search parameters. This can be one of the following:
29+
*
30+
* - `name`: Search and return package names only
31+
* - `vendor`: Search and return vendors only
32+
*
33+
* @var string|null
34+
*/
35+
public ?string $limitTo = null;
36+
37+
/**
38+
* @var array<int, mixed> The results returned from the query.
39+
*/
40+
public array $results = [];
41+
42+
/**
43+
* Command handler.
44+
*/
45+
public function handle(
46+
string $query,
47+
?string $type = null,
48+
bool $onlyNames = false,
49+
bool $onlyVendors = false
50+
): void {
51+
$this->query = $query;
52+
$this->type = $type;
53+
54+
if ($onlyNames) {
55+
$this->limitTo = 'name';
56+
} elseif ($onlyVendors) {
57+
$this->limitTo = 'vendor';
58+
}
59+
}
60+
61+
/**
62+
* @inheritDoc
63+
*/
64+
public function execute()
65+
{
66+
$output = $this->runComposerCommand();
67+
68+
if ($output['code'] !== 0) {
69+
throw new CommandException(implode(PHP_EOL, $output['output']));
70+
}
71+
72+
$this->results = json_decode(implode(PHP_EOL, $output['output']), true);
73+
74+
return $this;
75+
}
76+
77+
/**
78+
* @inheritDoc
79+
*/
80+
public function getCommandName(): string
81+
{
82+
return 'search';
83+
}
84+
85+
/**
86+
* @inheritDoc
87+
*/
88+
public function requiresWorkDir(): bool
89+
{
90+
return false;
91+
}
92+
93+
/**
94+
* Returns the list of results found.
95+
*
96+
* @return array<int, mixed>
97+
*/
98+
public function getResults(): array
99+
{
100+
return $this->results;
101+
}
102+
103+
/**
104+
* Returns the number of results found.
105+
*/
106+
public function count(): int
107+
{
108+
return count($this->results);
109+
}
110+
111+
/**
112+
* @inheritDoc
113+
*/
114+
public function arguments(): array
115+
{
116+
$arguments = [];
117+
118+
if (!empty($this->type)) {
119+
$arguments['--type'] = $this->type;
120+
}
121+
122+
if ($this->limitTo === 'name') {
123+
$arguments['--only-name'] = true;
124+
} elseif ($this->limitTo === 'vendor') {
125+
$arguments['--only-vendor'] = true;
126+
}
127+
128+
$arguments['--format'] = 'json';
129+
130+
$arguments['tokens'] = preg_split('/ +/', $this->query, -1, PREG_SPLIT_NO_EMPTY);
131+
132+
return $arguments;
133+
}
134+
}

src/Composer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class Composer
5151
protected $commands = [
5252
'i' => \Winter\Packager\Commands\Install::class,
5353
'install' => \Winter\Packager\Commands\Install::class,
54+
'search' => \Winter\Packager\Commands\Search::class,
5455
'show' => \Winter\Packager\Commands\Show::class,
5556
'update' => \Winter\Packager\Commands\Update::class,
5657
'version' => \Winter\Packager\Commands\Version::class,

src/Exceptions/ComposerExceptionHandler.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
* Handles a Composer exception and returns a corresponding Packager exception.
1010
*
1111
* @author Ben Thomson
12-
* @since 0.1.6
12+
* @since 0.2.0
1313
*/
1414
class ComposerExceptionHandler
1515
{
1616
/**
1717
* Handles a Composer exception and returns a corresponding Packager exception.
1818
*
19-
* @param Throwable $exception
19+
* @param \Throwable $exception
2020
* @param BaseCommand $command
2121
* @return array<string, mixed>
2222
*/
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Winter\Packager\Tests\Cases;
6+
7+
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
8+
use Winter\Packager\Commands\Search;
9+
use Winter\Packager\Tests\ComposerTestCase;
10+
11+
/**
12+
* @testdox The Search command
13+
* @coversDefaultClass \Winter\Packager\Commands\Search
14+
*/
15+
final class SearchTest extends ComposerTestCase
16+
{
17+
use ArraySubsetAsserts;
18+
19+
/**
20+
* @test
21+
* @testdox can run a (mocked) search and show a few results.
22+
* @covers ::handle
23+
* @covers ::execute
24+
* @covers ::getResults
25+
* @covers ::count
26+
*/
27+
public function itCanRunAMockedSearch(): void
28+
{
29+
$this->mockCommandOutput(
30+
'search',
31+
Search::class,
32+
0,
33+
json_encode([
34+
[
35+
'name' => 'winter/wn-system-module',
36+
'description' => 'System module for Winter CMS',
37+
'url' => 'https://packagist.org/packages/winter/wn-system-module',
38+
'repository' => 'https://github.com/wintercms/wn-system-module',
39+
],
40+
[
41+
'name' => 'winter/wn-cms-module',
42+
'description' => 'CMS module for Winter CMS',
43+
'url' => 'https://packagist.org/packages/winter/wn-cms-module',
44+
'repository' => 'https://github.com/wintercms/wn-cms-module',
45+
],
46+
[
47+
'name' => 'winter/wn-backend-module',
48+
'description' => 'Backend module for Winter CMS',
49+
'url' => 'https://packagist.org/packages/winter/wn-backend-module',
50+
'repository' => 'https://github.com/wintercms/wn-backend-module',
51+
],
52+
], JSON_PRETTY_PRINT)
53+
);
54+
55+
$search = $this->composer->search('winter', 'winter-module');
56+
57+
$this->assertArraySubset([
58+
[
59+
'name' => 'winter/wn-system-module',
60+
],
61+
[
62+
'name' => 'winter/wn-cms-module',
63+
],
64+
[
65+
'name' => 'winter/wn-backend-module',
66+
],
67+
], $search->getResults());
68+
$this->assertEquals(3, $search->count());
69+
}
70+
71+
/**
72+
* @test
73+
* @testdox can run a real search and show a few results.
74+
* @covers ::handle
75+
* @covers ::execute
76+
* @covers ::getResults
77+
* @covers ::count
78+
*/
79+
public function itCanRunRealSearch(): void
80+
{
81+
$search = $this->composer->search('winter', 'winter-module');
82+
83+
$this->assertArraySubset([
84+
[
85+
'name' => 'winter/wn-system-module',
86+
],
87+
[
88+
'name' => 'winter/wn-cms-module',
89+
],
90+
[
91+
'name' => 'winter/wn-backend-module',
92+
],
93+
], $search->getResults());
94+
$this->assertEquals(3, $search->count());
95+
}
96+
}

0 commit comments

Comments
 (0)