zhamao-framework/src/ZM/Module/ModulePacker.php
2022-04-03 01:31:04 +08:00

263 lines
8.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
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
{
public const ZM_MODULE_PACKER_VERSION = '1.1';
/** @var array */
private $module = [];
/** @var bool */
private $override = false;
/** @var string */
private $output_path = '';
/** @var string */
private $filename = '';
/** @var Phar */
private $phar;
/** @var null|array */
private $module_config;
/**
* @throws ModulePackException
*/
public function __construct(array $module)
{
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;
}
/**
* 设置输出文件夹
* @param string $path 输出路径
*/
public function setOutputPath(string $path)
{
$this->output_path = $path;
if (!is_dir($path)) {
mkdir($path, 0755, true);
}
}
/**
* 设置是否覆盖
*/
public function setOverride(bool $override = true)
{
$this->override = $override;
}
/**
* 获取模块名字
* @return mixed
*/
public function getName()
{
return $this->module['name'];
}
/**
* 获取打包的文件名绝对路径
*/
public function getFileName(): string
{
return $this->filename;
}
/**
* 打包模块
* @throws ZMException
*/
public function pack()
{
$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);
$this->addFiles(); // 添加文件
$this->addLightCacheStore(); // 保存light-cache-store指定的项
$this->addModuleConfig(); // 生成module-config.json
$this->addZMDataFiles(); // 添加需要保存的zm_data下的目录或文件
$this->addEntry(); // 生成模块的入口文件module_entry.php
$this->phar->stopBuffering();
}
private function addFiles()
{
$file_list = DataProvider::scanDirFiles($this->module['module-path'], true, false);
foreach ($file_list as $v) {
$this->phar->addFile($v, $this->getRelativePath($v));
}
}
private function getRelativePath($path)
{
return str_replace(realpath(DataProvider::getSourceRootDir()) . '/', '', realpath($path));
}
private function generatePharAutoload(): array
{
$pos = strpos($this->module['module-path'], DataProvider::getSourceRootDir() . '/');
if ($pos === 0) {
$path_value = substr($this->module['module-path'], strlen(DataProvider::getSourceRootDir() . '/'));
} else {
throw new ModulePackException(zm_internal_errcode('E99999')); // 未定义的错误
}
return ZMUtil::getClassesPsr4($this->module['module-path'], $this->module['namespace'], null, $path_value);
}
private function getComposerAutoloadItems(): array
{
$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) {
if (strpos($v, $path) === 0) {
$item['files'][] = $v;
}
}
return $item;
}
/**
* @throws ZMException
* @throws Exception
*/
private function addLightCacheStore()
{
if (isset($this->module['light-cache-store'])) {
$store = [];
$r = ZMConfig::get('global', 'light_cache') ?? [
'size' => 512, // 最多允许储存的条数需要2的倍数
'max_strlen' => 32768, // 单行字符串最大长度需要2的倍数
'hash_conflict_proportion' => 0.6, // Hash冲突率越大越好但是需要的内存更多
'persistence_path' => DataProvider::getDataFolder() . '_cache.json',
'auto_save_interval' => 900,
];
LightCache::init($r);
foreach ($this->module['light-cache-store'] as $v) {
$r = LightCache::get($v);
if ($r === null) {
Console::warning(zm_internal_errcode('E00045') . 'LightCache 项:' . $v . ' 不存在或值为null无法为其保存。');
} else {
$store[$v] = $r;
Console::info('打包LightCache持久化项' . $v);
}
}
$this->phar->addFromString('light_cache_store.json', json_encode($store, 128 | 256));
}
}
private function addModuleConfig()
{
$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'],
'hotload-psr-4' => $this->generatePharAutoload(),
'unpack' => [
'composer-autoload-items' => $this->getComposerAutoloadItems(),
'global-config-override' => $this->module['global-config-override'] ?? false,
],
'allow-hotload' => $this->module['allow-hotload'] ?? false,
'pack-time' => time(),
];
if (isset($stub_values['unpack']['composer-autoload-items']['files'])) {
$stub_values['hotload-files'] = $stub_values['unpack']['composer-autoload-items']['files'];
}
$this->phar->addFromString('zmplugin.json', json_encode($stub_values, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
$this->module_config = $stub_values;
}
private function addEntry()
{
$stub_replace = [
'generated_id' => $this->module_config['generated-id'],
];
$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'));
}
/**
* @throws ModulePackException
*/
private function addZMDataFiles()
{
$base_dir = realpath(DataProvider::getDataFolder());
if (is_array($this->module['zm-data-store'] ?? null)) {
foreach ($this->module['zm-data-store'] as $v) {
if (is_dir($base_dir . '/' . $v)) {
$v = rtrim($v, '/');
Console::info('Adding external zm_data dir: ' . $v);
$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)) {
Console::info('Add external zm_data file: ' . $v);
$this->phar->addFile($base_dir . '/' . $v, 'zm_data/' . $v);
} else {
throw new ModulePackException(zm_internal_errcode('E00066') . '`zmdata-store` 指定的文件或目录不存在');
}
}
}
}
}