Skip to content

Commit fc4f93a

Browse files
authored
Improved packaging speed for command phar:build. (#7040)
1 parent 702882c commit fc4f93a

6 files changed

Lines changed: 141 additions & 16 deletions

File tree

src/Ast/Visitor/RewriteConfigVisitor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
class RewriteConfigVisitor extends NodeVisitorAbstract
1919
{
20-
public function leaveNode(Node $node)
20+
public function leaveNode(Node $node): Node|Node\Stmt\Expression
2121
{
2222
if ($node instanceof Node\Stmt\Return_) {
2323
$result = new Node\Expr\Variable('result');

src/Ast/Visitor/UnshiftCodeStringVisitor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function __construct(public string $code)
2727
$this->astParser = $parserFactory->create(ParserFactory::ONLY_PHP7);
2828
}
2929

30-
public function beforeTraverse(array $nodes)
30+
public function beforeTraverse(array $nodes): ?array
3131
{
3232
$stmt = $this->astParser->parse($this->code);
3333
foreach ($nodes as $i => $node) {

src/BuildCommand.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function __construct(protected ContainerInterface $container)
2626
parent::__construct('phar:build');
2727
}
2828

29-
public function configure()
29+
public function configure(): void
3030
{
3131
$this->setDescription('Pack your project into a Phar package.')
3232
->addOption('name', '', InputOption::VALUE_OPTIONAL, 'This is the name of the Phar package, and if it is not passed in, the project name is used by default')
@@ -36,7 +36,7 @@ public function configure()
3636
->addOption('mount', 'M', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The mount path or dir.');
3737
}
3838

39-
public function handle()
39+
public function handle(): void
4040
{
4141
$this->assertWritable();
4242
$name = $this->input->getOption('name');
@@ -68,7 +68,7 @@ public function handle()
6868
/**
6969
* check readonly.
7070
*/
71-
public function assertWritable()
71+
public function assertWritable(): void
7272
{
7373
if (ini_get('phar.readonly') === '1') {
7474
throw new UnexpectedValueException('Your configuration disabled writing phar files (phar.readonly = On), please update your configuration');
@@ -84,7 +84,6 @@ public function getPharBuilder(string $path): PharBuilder
8484
throw new InvalidArgumentException(sprintf('The given path %s is not a readable file', $path));
8585
}
8686
$pharBuilder = new PharBuilder($path, $this->container->get(LoggerInterface::class));
87-
8887
$vendorPath = $pharBuilder->getPackage()->getVendorAbsolutePath();
8988
if (! is_dir($vendorPath)) {
9089
throw new RuntimeException('The project has not been initialized, please manually execute the command `composer install` to install the dependencies');

src/CustomPhar.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of Hyperf.
6+
*
7+
* @link https://www.hyperf.io
8+
* @document https://hyperf.wiki
9+
* @contact group@hyperf.io
10+
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
11+
*/
12+
13+
namespace Hyperf\Phar;
14+
15+
use FilesystemIterator;
16+
use Phar;
17+
use RecursiveDirectoryIterator;
18+
use RecursiveIteratorIterator;
19+
use RuntimeException;
20+
use SplFileInfo;
21+
22+
class CustomPhar extends Phar
23+
{
24+
private string $tempDir;
25+
26+
public function __construct(string $filename, int $flags = FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS, ?string $alias = null)
27+
{
28+
parent::__construct($filename, $flags, $alias);
29+
$this->tempDir = sys_get_temp_dir() . '/phar_cache_' . uniqid();
30+
$this->createDirectory($this->tempDir);
31+
}
32+
33+
public function addFile(string $filename, ?string $localName = null): void
34+
{
35+
$localName = $localName ?? basename($filename);
36+
$relativePath = $this->tempDir . '/' . $localName;
37+
$this->createDirectory(dirname($relativePath));
38+
copy($filename, $relativePath);
39+
}
40+
41+
public function addFromString(string $localName, string $contents): void
42+
{
43+
$relativePath = $this->tempDir . '/' . $localName;
44+
$this->createDirectory(dirname($relativePath));
45+
file_put_contents($relativePath, $contents);
46+
}
47+
48+
public function buildFromDirectory(string $directory, ?string $pattern = null): array
49+
{
50+
$this->recursiveCopy($directory, $this->tempDir, $pattern);
51+
return [];
52+
}
53+
54+
public function buildFromIterator($iterator, ?string $baseDirectory = null): array
55+
{
56+
foreach ($iterator as $fileInfo) {
57+
/** @var SplFileInfo $fileInfo */
58+
$relativePath = $baseDirectory ? str_replace(rtrim($baseDirectory, '/\\') . '/', '', $fileInfo->getRealPath()) : $fileInfo->getFilename();
59+
$this->addFile($fileInfo->getRealPath(), $relativePath);
60+
}
61+
return [];
62+
}
63+
64+
/**
65+
* 批量保存文件.
66+
*/
67+
public function save(): void
68+
{
69+
parent::buildFromDirectory($this->tempDir);
70+
$this->clearTempDir();
71+
}
72+
73+
/**
74+
* 资源文件复制.
75+
*/
76+
private function recursiveCopy(string $source, string $destination, ?string $pattern = null): void
77+
{
78+
$iterator = new RecursiveIteratorIterator(
79+
new RecursiveDirectoryIterator($source, FilesystemIterator::SKIP_DOTS),
80+
RecursiveIteratorIterator::SELF_FIRST
81+
);
82+
83+
foreach ($iterator as $item) {
84+
$targetPath = $destination . '/' . substr($item->getRealPath(), strlen($source) + 1);
85+
if ($item->isDir()) {
86+
$this->createDirectory($targetPath);
87+
} elseif (! $pattern || preg_match($pattern, $item->getFilename())) {
88+
copy($item->getRealPath(), $targetPath);
89+
}
90+
}
91+
}
92+
93+
/**
94+
* 创建目录结构.
95+
*/
96+
private function createDirectory(string $dir): void
97+
{
98+
if (! is_dir($dir) && ! mkdir($dir, 0777, true) && ! is_dir($dir)) {
99+
throw new RuntimeException("Directory {$dir} was not created");
100+
}
101+
}
102+
103+
/**
104+
* 清理临时目录.
105+
*/
106+
private function clearTempDir(): void
107+
{
108+
$files = new RecursiveIteratorIterator(
109+
new RecursiveDirectoryIterator($this->tempDir, FilesystemIterator::SKIP_DOTS),
110+
RecursiveIteratorIterator::CHILD_FIRST
111+
);
112+
113+
foreach ($files as $fileinfo) {
114+
($fileinfo->isDir() ? 'rmdir' : 'unlink')($fileinfo->getRealPath());
115+
}
116+
117+
rmdir($this->tempDir);
118+
}
119+
}

src/PharBuilder.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ public function getMountLinkCode(): string
230230
/**
231231
* Compile the code into the Phar file.
232232
*/
233-
public function build()
233+
public function build(): void
234234
{
235235
$this->logger->info('Creating phar <info>' . $this->getTarget() . '</info>');
236236
$time = microtime(true);
@@ -248,7 +248,7 @@ public function build()
248248

249249
$main = $this->getMain();
250250

251-
$targetPhar = new TargetPhar(new Phar($tmp), $this);
251+
$targetPhar = new TargetPhar(new CustomPhar($tmp), $this);
252252
$this->logger->info('Adding main package "' . $this->package->getName() . '"');
253253
$finder = Finder::create()
254254
->files()
@@ -333,8 +333,10 @@ public function build()
333333
$this->logger->info('Adding main file "' . $main . '"');
334334
$this->rewriteMainWithMountLinkCode($targetPhar, $main);
335335

336+
$this->logger->info('Packaging all cache files into the PHAR archive.');
337+
$targetPhar->save();
338+
336339
$this->logger->info('Setting stub');
337-
// Add the default stub.
338340
$targetPhar->setStub($targetPhar->createDefaultStub($main));
339341
$this->logger->info('Setting default stub <info>' . $main . '</info>.');
340342

@@ -357,7 +359,7 @@ public function build()
357359
/**
358360
* Find the scan_cacheable configuration and force it to open.
359361
*/
360-
protected function enableScanCacheable(TargetPhar $targetPhar)
362+
protected function enableScanCacheable(TargetPhar $targetPhar): void
361363
{
362364
$configPath = 'config/config.php';
363365
$absPath = $this->package->getDirectory() . $configPath;
@@ -372,7 +374,7 @@ protected function enableScanCacheable(TargetPhar $targetPhar)
372374
/**
373375
* Replace the method in the Config component to get the true path to the configuration file.
374376
*/
375-
protected function replaceConfigFactoryReadPaths(TargetPhar $targetPhar, string $vendorPath)
377+
protected function replaceConfigFactoryReadPaths(TargetPhar $targetPhar, string $vendorPath): void
376378
{
377379
$configPath = 'hyperf/config/src/ConfigFactory.php';
378380
$absPath = $vendorPath . $configPath;
@@ -407,10 +409,8 @@ private function loadJson(string $path): array
407409

408410
/**
409411
* Get file size.
410-
*
411-
* @param PharBuilder|string $path
412412
*/
413-
private function getSize($path): string
413+
private function getSize(PharBuilder|string $path): string
414414
{
415415
return round(filesize((string) $path) / 1024, 1) . ' KiB';
416416
}

src/TargetPhar.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212

1313
namespace Hyperf\Phar;
1414

15-
use Phar;
1615
use Stringable;
1716
use Symfony\Component\Finder\Finder;
1817
use Traversable;
1918

2019
class TargetPhar implements Stringable
2120
{
22-
public function __construct(private Phar $phar, private PharBuilder $pharBuilder)
21+
public function __construct(private readonly CustomPhar $phar, private readonly PharBuilder $pharBuilder)
2322
{
2423
$phar->startBuffering();
2524
}
@@ -98,4 +97,12 @@ public function addFromString(string $local, string $contents): void
9897
{
9998
$this->phar->addFromString($local, $contents);
10099
}
100+
101+
/**
102+
* 保存文件.
103+
*/
104+
public function save(): void
105+
{
106+
$this->phar->save();
107+
}
101108
}

0 commit comments

Comments
 (0)