2021-06-16 00:17:30 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
declare(strict_types=1);
|
2021-06-16 00:17:30 +08:00
|
|
|
|
|
|
|
|
|
|
namespace ZM\Module;
|
|
|
|
|
|
|
|
|
|
|
|
use Exception;
|
|
|
|
|
|
use Phar;
|
|
|
|
|
|
use ZM\Config\ZMConfig;
|
|
|
|
|
|
use ZM\Console\Console;
|
|
|
|
|
|
use ZM\Exception\ModulePackException;
|
|
|
|
|
|
use ZM\Exception\ZMException;
|
|
|
|
|
|
use ZM\Store\LightCache;
|
|
|
|
|
|
use ZM\Utils\DataProvider;
|
|
|
|
|
|
use ZM\Utils\ZMUtil;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 模块构造器
|
|
|
|
|
|
* Class ModulePacker
|
|
|
|
|
|
* @since 2.5
|
|
|
|
|
|
*/
|
|
|
|
|
|
class ModulePacker
|
|
|
|
|
|
{
|
2022-03-20 16:20:14 +08:00
|
|
|
|
public const ZM_MODULE_PACKER_VERSION = '1.1';
|
2022-03-15 18:05:33 +08:00
|
|
|
|
|
|
|
|
|
|
/** @var array */
|
2021-06-16 00:17:30 +08:00
|
|
|
|
private $module = [];
|
2022-03-15 18:05:33 +08:00
|
|
|
|
|
|
|
|
|
|
/** @var bool */
|
2021-06-16 00:17:30 +08:00
|
|
|
|
private $override = false;
|
2022-03-15 18:05:33 +08:00
|
|
|
|
|
|
|
|
|
|
/** @var string */
|
2021-06-16 00:17:30 +08:00
|
|
|
|
private $output_path = '';
|
2022-03-15 18:05:33 +08:00
|
|
|
|
|
|
|
|
|
|
/** @var string */
|
2021-06-16 00:17:30 +08:00
|
|
|
|
private $filename = '';
|
2022-03-15 18:05:33 +08:00
|
|
|
|
|
|
|
|
|
|
/** @var Phar */
|
2021-06-16 00:17:30 +08:00
|
|
|
|
private $phar;
|
2022-03-15 18:05:33 +08:00
|
|
|
|
|
|
|
|
|
|
/** @var null|array */
|
|
|
|
|
|
private $module_config;
|
2021-06-16 00:17:30 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @throws ModulePackException
|
|
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public function __construct(array $module)
|
|
|
|
|
|
{
|
2021-06-16 00:17:30 +08:00
|
|
|
|
if (ini_get('phar.readonly') == '1') {
|
|
|
|
|
|
throw new ModulePackException('请先在 php.ini 中设置 `phar.readonly = Off` 后再打包模块!');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!isset($module['name'], $module['module-path'], $module['namespace'])) {
|
|
|
|
|
|
throw new ModulePackException('模块打包需要至少传入name、module-path、namespace三个参数!');
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->module = $module;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置输出文件夹
|
2022-04-02 23:37:22 +08:00
|
|
|
|
* @param string $path 输出路径
|
2021-06-16 00:17:30 +08:00
|
|
|
|
*/
|
2022-04-02 23:37:22 +08:00
|
|
|
|
public function setOutputPath(string $path)
|
2022-03-15 18:05:33 +08:00
|
|
|
|
{
|
2021-06-16 00:17:30 +08:00
|
|
|
|
$this->output_path = $path;
|
2022-03-15 18:05:33 +08:00
|
|
|
|
if (!is_dir($path)) {
|
|
|
|
|
|
mkdir($path, 0755, true);
|
|
|
|
|
|
}
|
2021-06-16 00:17:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置是否覆盖
|
|
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public function setOverride(bool $override = true)
|
|
|
|
|
|
{
|
2021-06-16 00:17:30 +08:00
|
|
|
|
$this->override = $override;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取模块名字
|
|
|
|
|
|
* @return mixed
|
|
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public function getName()
|
|
|
|
|
|
{
|
2021-06-16 00:17:30 +08:00
|
|
|
|
return $this->module['name'];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取打包的文件名绝对路径
|
|
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public function getFileName(): string
|
|
|
|
|
|
{
|
2021-06-16 00:17:30 +08:00
|
|
|
|
return $this->filename;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 打包模块
|
|
|
|
|
|
* @throws ZMException
|
|
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
public function pack()
|
|
|
|
|
|
{
|
2021-06-16 00:17:30 +08:00
|
|
|
|
$this->filename = $this->output_path . '/' . $this->module['name'];
|
|
|
|
|
|
if (isset($this->module['version'])) {
|
|
|
|
|
|
$this->filename .= '_' . $this->module['version'];
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->filename .= '.phar';
|
|
|
|
|
|
if ($this->override) {
|
|
|
|
|
|
if (file_exists($this->filename)) {
|
|
|
|
|
|
Console::info('Overwriting ' . $this->filename);
|
|
|
|
|
|
unlink($this->filename);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->phar = new Phar($this->filename);
|
|
|
|
|
|
$this->phar->startBuffering();
|
|
|
|
|
|
Console::info('模块输出文件:' . $this->filename);
|
|
|
|
|
|
|
2022-03-20 16:20:14 +08:00
|
|
|
|
$this->addFiles(); // 添加文件
|
|
|
|
|
|
$this->addLightCacheStore(); // 保存light-cache-store指定的项
|
|
|
|
|
|
$this->addModuleConfig(); // 生成module-config.json
|
|
|
|
|
|
$this->addZMDataFiles(); // 添加需要保存的zm_data下的目录或文件
|
|
|
|
|
|
$this->addEntry(); // 生成模块的入口文件module_entry.php
|
2021-06-16 00:17:30 +08:00
|
|
|
|
|
|
|
|
|
|
$this->phar->stopBuffering();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
private function addFiles()
|
|
|
|
|
|
{
|
2021-06-16 00:17:30 +08:00
|
|
|
|
$file_list = DataProvider::scanDirFiles($this->module['module-path'], true, false);
|
|
|
|
|
|
foreach ($file_list as $v) {
|
|
|
|
|
|
$this->phar->addFile($v, $this->getRelativePath($v));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
private function getRelativePath($path)
|
|
|
|
|
|
{
|
2021-06-16 00:17:30 +08:00
|
|
|
|
return str_replace(realpath(DataProvider::getSourceRootDir()) . '/', '', realpath($path));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
private function generatePharAutoload(): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$pos = strpos($this->module['module-path'], DataProvider::getSourceRootDir() . '/');
|
2021-09-01 14:14:00 +08:00
|
|
|
|
if ($pos === 0) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
$path_value = substr($this->module['module-path'], strlen(DataProvider::getSourceRootDir() . '/'));
|
2021-09-01 14:14:00 +08:00
|
|
|
|
} else {
|
2022-03-20 16:20:14 +08:00
|
|
|
|
throw new ModulePackException(zm_internal_errcode('E99999')); // 未定义的错误
|
2021-09-01 14:14:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
return ZMUtil::getClassesPsr4($this->module['module-path'], $this->module['namespace'], null, $path_value);
|
2021-06-16 00:17:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
private function getComposerAutoloadItems(): array
|
|
|
|
|
|
{
|
2021-06-16 00:17:30 +08:00
|
|
|
|
$composer = json_decode(file_get_contents(DataProvider::getSourceRootDir() . '/composer.json'), true);
|
|
|
|
|
|
$path = self::getRelativePath($this->module['module-path']);
|
|
|
|
|
|
$item = [];
|
|
|
|
|
|
foreach (($composer['autoload']['psr-4'] ?? []) as $k => $v) {
|
|
|
|
|
|
if (strpos($path, $v) === 0) {
|
|
|
|
|
|
$item['psr-4'][$k] = $v;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach (($composer['autoload']['files'] ?? []) as $v) {
|
2022-03-20 16:20:14 +08:00
|
|
|
|
if (strpos($v, $path) === 0) {
|
2021-06-16 00:17:30 +08:00
|
|
|
|
$item['files'][] = $v;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return $item;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @throws ZMException
|
|
|
|
|
|
* @throws Exception
|
|
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
private function addLightCacheStore()
|
|
|
|
|
|
{
|
2021-06-16 00:17:30 +08:00
|
|
|
|
if (isset($this->module['light-cache-store'])) {
|
|
|
|
|
|
$store = [];
|
|
|
|
|
|
$r = ZMConfig::get('global', 'light_cache') ?? [
|
2022-03-20 16:20:14 +08:00
|
|
|
|
'size' => 512, // 最多允许储存的条数(需要2的倍数)
|
|
|
|
|
|
'max_strlen' => 32768, // 单行字符串最大长度(需要2的倍数)
|
|
|
|
|
|
'hash_conflict_proportion' => 0.6, // Hash冲突率(越大越好,但是需要的内存更多)
|
2022-03-15 18:05:33 +08:00
|
|
|
|
'persistence_path' => DataProvider::getDataFolder() . '_cache.json',
|
|
|
|
|
|
'auto_save_interval' => 900,
|
|
|
|
|
|
];
|
2021-06-16 00:17:30 +08:00
|
|
|
|
LightCache::init($r);
|
|
|
|
|
|
foreach ($this->module['light-cache-store'] as $v) {
|
|
|
|
|
|
$r = LightCache::get($v);
|
|
|
|
|
|
if ($r === null) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
Console::warning(zm_internal_errcode('E00045') . 'LightCache 项:' . $v . ' 不存在或值为null,无法为其保存。');
|
2021-06-16 00:17:30 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
$store[$v] = $r;
|
|
|
|
|
|
Console::info('打包LightCache持久化项:' . $v);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->phar->addFromString('light_cache_store.json', json_encode($store, 128 | 256));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
private function addModuleConfig()
|
|
|
|
|
|
{
|
2021-06-16 00:17:30 +08:00
|
|
|
|
$stub_values = [
|
|
|
|
|
|
'zm-module' => true,
|
|
|
|
|
|
'generated-id' => sha1(strval(microtime(true))),
|
|
|
|
|
|
'module-packer-version' => self::ZM_MODULE_PACKER_VERSION,
|
|
|
|
|
|
'module-root-path' => $this->getRelativePath($this->module['module-path']),
|
|
|
|
|
|
'namespace' => $this->module['namespace'],
|
2022-03-20 16:20:14 +08:00
|
|
|
|
'hotload-psr-4' => $this->generatePharAutoload(),
|
2021-06-16 00:17:30 +08:00
|
|
|
|
'unpack' => [
|
|
|
|
|
|
'composer-autoload-items' => $this->getComposerAutoloadItems(),
|
2022-03-15 18:05:33 +08:00
|
|
|
|
'global-config-override' => $this->module['global-config-override'] ?? false,
|
2021-06-16 00:17:30 +08:00
|
|
|
|
],
|
2022-03-15 18:05:33 +08:00
|
|
|
|
'allow-hotload' => $this->module['allow-hotload'] ?? false,
|
|
|
|
|
|
'pack-time' => time(),
|
2021-06-16 00:17:30 +08:00
|
|
|
|
];
|
2022-03-20 16:20:14 +08:00
|
|
|
|
if (isset($stub_values['unpack']['composer-autoload-items']['files'])) {
|
|
|
|
|
|
$stub_values['hotload-files'] = $stub_values['unpack']['composer-autoload-items']['files'];
|
|
|
|
|
|
}
|
2021-06-16 00:17:30 +08:00
|
|
|
|
$this->phar->addFromString('zmplugin.json', json_encode($stub_values, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
|
|
|
|
|
$this->module_config = $stub_values;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-15 18:05:33 +08:00
|
|
|
|
private function addEntry()
|
|
|
|
|
|
{
|
2021-06-16 00:17:30 +08:00
|
|
|
|
$stub_replace = [
|
2022-03-15 18:05:33 +08:00
|
|
|
|
'generated_id' => $this->module_config['generated-id'],
|
2021-06-16 00:17:30 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
$stub_template = str_replace(
|
|
|
|
|
|
array_map(function ($x) {
|
|
|
|
|
|
return '__' . $x . '__';
|
|
|
|
|
|
}, array_keys($stub_replace)),
|
|
|
|
|
|
array_values($stub_replace),
|
|
|
|
|
|
file_get_contents(DataProvider::getFrameworkRootDir() . '/src/ZM/script_phar_stub.php')
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->phar->addFromString('module_entry.php', $stub_template);
|
|
|
|
|
|
|
|
|
|
|
|
$this->phar->setStub($this->phar->createDefaultStub('module_entry.php'));
|
|
|
|
|
|
}
|
2021-07-04 15:45:30 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @throws ModulePackException
|
|
|
|
|
|
*/
|
2022-03-15 18:05:33 +08:00
|
|
|
|
private function addZMDataFiles()
|
|
|
|
|
|
{
|
2021-07-04 15:45:30 +08:00
|
|
|
|
$base_dir = realpath(DataProvider::getDataFolder());
|
2022-03-15 18:05:33 +08:00
|
|
|
|
if (is_array($this->module['zm-data-store'] ?? null)) {
|
|
|
|
|
|
foreach ($this->module['zm-data-store'] as $v) {
|
2021-07-04 15:45:30 +08:00
|
|
|
|
if (is_dir($base_dir . '/' . $v)) {
|
|
|
|
|
|
$v = rtrim($v, '/');
|
2022-03-15 18:05:33 +08:00
|
|
|
|
Console::info('Adding external zm_data dir: ' . $v);
|
2021-07-04 15:45:30 +08:00
|
|
|
|
$files = DataProvider::scanDirFiles($base_dir . '/' . $v, true, true);
|
|
|
|
|
|
foreach ($files as $single) {
|
|
|
|
|
|
$this->phar->addFile($base_dir . '/' . $v . '/' . $single, 'zm_data/' . $v . '/' . $single);
|
|
|
|
|
|
}
|
|
|
|
|
|
} elseif (is_file($base_dir . '/' . $v)) {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
Console::info('Add external zm_data file: ' . $v);
|
2021-07-04 15:45:30 +08:00
|
|
|
|
$this->phar->addFile($base_dir . '/' . $v, 'zm_data/' . $v);
|
|
|
|
|
|
} else {
|
2022-03-15 18:05:33 +08:00
|
|
|
|
throw new ModulePackException(zm_internal_errcode('E00066') . '`zmdata-store` 指定的文件或目录不存在');
|
2021-07-04 15:45:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-03-15 18:05:33 +08:00
|
|
|
|
}
|