2025-11-30 15:35:04 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace StaticPHP\Exception;
|
|
|
|
|
|
2026-02-15 21:58:42 +08:00
|
|
|
use StaticPHP\Package\LibraryPackage;
|
|
|
|
|
use StaticPHP\Package\Package;
|
|
|
|
|
use StaticPHP\Package\PackageBuilder;
|
|
|
|
|
use StaticPHP\Package\PackageInstaller;
|
|
|
|
|
use StaticPHP\Package\PhpExtensionPackage;
|
|
|
|
|
use StaticPHP\Package\TargetPackage;
|
2025-11-30 15:35:04 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Base class for SPC exceptions.
|
|
|
|
|
*
|
|
|
|
|
* This class serves as the base for all exceptions thrown by the SPC framework.
|
|
|
|
|
* It extends the built-in PHP Exception class, allowing for custom exception handling
|
|
|
|
|
* and categorization of SPC-related errors.
|
|
|
|
|
*/
|
|
|
|
|
abstract class SPCException extends \Exception
|
|
|
|
|
{
|
2026-02-28 13:43:28 +08:00
|
|
|
protected bool $simple_output = false;
|
|
|
|
|
|
2026-02-15 21:58:42 +08:00
|
|
|
/** @var null|array Package information */
|
|
|
|
|
private ?array $package_info = null;
|
2025-11-30 15:35:04 +08:00
|
|
|
|
2026-02-15 21:58:42 +08:00
|
|
|
/** @var null|array Package builder information */
|
|
|
|
|
private ?array $package_builder_info = null;
|
2025-11-30 15:35:04 +08:00
|
|
|
|
2026-02-15 21:58:42 +08:00
|
|
|
/** @var null|array Package installer information */
|
|
|
|
|
private ?array $package_installer_info = null;
|
|
|
|
|
|
|
|
|
|
/** @var array Stage execution call stack */
|
|
|
|
|
private array $stage_stack = [];
|
2025-11-30 15:35:04 +08:00
|
|
|
|
|
|
|
|
private array $extra_log_files = [];
|
|
|
|
|
|
|
|
|
|
public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null)
|
|
|
|
|
{
|
|
|
|
|
parent::__construct($message, $code, $previous);
|
|
|
|
|
$this->loadStackTraceInfo();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-15 21:58:42 +08:00
|
|
|
/**
|
|
|
|
|
* Bind package information manually.
|
|
|
|
|
*
|
|
|
|
|
* @param array $package_info Package information array
|
|
|
|
|
*/
|
|
|
|
|
public function bindPackageInfo(array $package_info): void
|
2025-11-30 15:35:04 +08:00
|
|
|
{
|
2026-02-15 21:58:42 +08:00
|
|
|
$this->package_info = $package_info;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add stage to the call stack.
|
|
|
|
|
* This builds a call chain like: build -> configure -> compile
|
|
|
|
|
*
|
|
|
|
|
* @param string $stage_name Stage name being executed
|
|
|
|
|
* @param array $context Stage context (optional)
|
|
|
|
|
*/
|
|
|
|
|
public function addStageToStack(string $stage_name, array $context = []): void
|
|
|
|
|
{
|
|
|
|
|
$this->stage_stack[] = [
|
|
|
|
|
'stage_name' => $stage_name,
|
|
|
|
|
'context_keys' => array_keys($context),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Legacy method for backward compatibility.
|
|
|
|
|
* @deprecated Use addStageToStack() instead
|
|
|
|
|
*/
|
|
|
|
|
public function bindStageInfo(string $stage_name, array $context = []): void
|
|
|
|
|
{
|
|
|
|
|
$this->addStageToStack($stage_name, $context);
|
2025-11-30 15:35:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function addExtraLogFile(string $key, string $filename): void
|
|
|
|
|
{
|
|
|
|
|
$this->extra_log_files[$key] = $filename;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-15 21:58:42 +08:00
|
|
|
* Returns package information.
|
2025-11-30 15:35:04 +08:00
|
|
|
*
|
|
|
|
|
* @return null|array{
|
2026-02-15 21:58:42 +08:00
|
|
|
* package_name: string,
|
|
|
|
|
* package_type: string,
|
|
|
|
|
* package_class: string,
|
2025-11-30 15:35:04 +08:00
|
|
|
* file: null|string,
|
|
|
|
|
* line: null|int,
|
2026-02-15 21:58:42 +08:00
|
|
|
* } Package information or null
|
2025-11-30 15:35:04 +08:00
|
|
|
*/
|
2026-02-15 21:58:42 +08:00
|
|
|
public function getPackageInfo(): ?array
|
2025-11-30 15:35:04 +08:00
|
|
|
{
|
2026-02-15 21:58:42 +08:00
|
|
|
return $this->package_info;
|
2025-11-30 15:35:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-15 21:58:42 +08:00
|
|
|
* Returns package builder information.
|
2025-11-30 15:35:04 +08:00
|
|
|
*
|
|
|
|
|
* @return null|array{
|
|
|
|
|
* file: null|string,
|
|
|
|
|
* line: null|int,
|
2026-02-15 21:58:42 +08:00
|
|
|
* method: null|string,
|
|
|
|
|
* } Package builder information or null
|
2025-11-30 15:35:04 +08:00
|
|
|
*/
|
2026-02-15 21:58:42 +08:00
|
|
|
public function getPackageBuilderInfo(): ?array
|
2025-11-30 15:35:04 +08:00
|
|
|
{
|
2026-02-15 21:58:42 +08:00
|
|
|
return $this->package_builder_info;
|
2025-11-30 15:35:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-15 21:58:42 +08:00
|
|
|
* Returns package installer information.
|
2025-11-30 15:35:04 +08:00
|
|
|
*
|
|
|
|
|
* @return null|array{
|
|
|
|
|
* file: null|string,
|
|
|
|
|
* line: null|int,
|
2026-02-15 21:58:42 +08:00
|
|
|
* method: null|string,
|
|
|
|
|
* } Package installer information or null
|
2025-11-30 15:35:04 +08:00
|
|
|
*/
|
2026-02-15 21:58:42 +08:00
|
|
|
public function getPackageInstallerInfo(): ?array
|
2025-11-30 15:35:04 +08:00
|
|
|
{
|
2026-02-15 21:58:42 +08:00
|
|
|
return $this->package_installer_info;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the stage call stack.
|
|
|
|
|
*
|
|
|
|
|
* @return array<array{
|
|
|
|
|
* stage_name: string,
|
|
|
|
|
* context_keys: array<string>,
|
|
|
|
|
* }> Stage call stack (empty array if no stages)
|
|
|
|
|
*/
|
|
|
|
|
public function getStageStack(): array
|
|
|
|
|
{
|
|
|
|
|
return $this->stage_stack;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the innermost (actual failing) stage information.
|
|
|
|
|
* Legacy method for backward compatibility.
|
|
|
|
|
*
|
|
|
|
|
* @return null|array{
|
|
|
|
|
* stage_name: string,
|
|
|
|
|
* context_keys: array<string>,
|
|
|
|
|
* } Stage information or null
|
|
|
|
|
*/
|
|
|
|
|
public function getStageInfo(): ?array
|
|
|
|
|
{
|
|
|
|
|
return empty($this->stage_stack) ? null : end($this->stage_stack);
|
2025-11-30 15:35:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getExtraLogFiles(): array
|
|
|
|
|
{
|
|
|
|
|
return $this->extra_log_files;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 13:43:28 +08:00
|
|
|
public function isSimpleOutput(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->simple_output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setSimpleOutput(bool $simple_output = true): void
|
|
|
|
|
{
|
|
|
|
|
$this->simple_output = $simple_output;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-15 21:58:42 +08:00
|
|
|
/**
|
|
|
|
|
* Load stack trace information to detect Package, Builder, and Installer context.
|
|
|
|
|
*/
|
2025-11-30 15:35:04 +08:00
|
|
|
private function loadStackTraceInfo(): void
|
|
|
|
|
{
|
|
|
|
|
$trace = $this->getTrace();
|
|
|
|
|
foreach ($trace as $frame) {
|
|
|
|
|
if (!isset($frame['class'])) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-15 21:58:42 +08:00
|
|
|
// Check if the class is a Package subclass
|
|
|
|
|
if (!$this->package_info && is_a($frame['class'], Package::class, true)) {
|
2025-11-30 15:35:04 +08:00
|
|
|
try {
|
2026-02-15 21:58:42 +08:00
|
|
|
// Try to get package information from object if available
|
|
|
|
|
if (isset($frame['object']) && $frame['object'] instanceof Package) {
|
|
|
|
|
$package = $frame['object'];
|
|
|
|
|
$package_type = match (true) {
|
|
|
|
|
$package instanceof LibraryPackage => 'library',
|
|
|
|
|
$package instanceof PhpExtensionPackage => 'php-extension',
|
2026-02-17 18:33:21 +08:00
|
|
|
/* @phpstan-ignore-next-line */
|
2026-02-15 21:58:42 +08:00
|
|
|
$package instanceof TargetPackage => 'target',
|
|
|
|
|
default => 'package',
|
|
|
|
|
};
|
|
|
|
|
$this->package_info = [
|
|
|
|
|
'package_name' => $package->name,
|
|
|
|
|
'package_type' => $package_type,
|
|
|
|
|
'package_class' => $frame['class'],
|
|
|
|
|
'file' => $frame['file'] ?? null,
|
|
|
|
|
'line' => $frame['line'] ?? null,
|
|
|
|
|
];
|
|
|
|
|
continue;
|
2025-11-30 15:35:04 +08:00
|
|
|
}
|
2026-02-15 21:58:42 +08:00
|
|
|
} catch (\Throwable) {
|
|
|
|
|
// Ignore reflection errors
|
2025-11-30 15:35:04 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-15 21:58:42 +08:00
|
|
|
// Check if the class is PackageBuilder
|
|
|
|
|
if (!$this->package_builder_info && is_a($frame['class'], PackageBuilder::class, true)) {
|
|
|
|
|
$this->package_builder_info = [
|
2025-11-30 15:35:04 +08:00
|
|
|
'file' => $frame['file'] ?? null,
|
|
|
|
|
'line' => $frame['line'] ?? null,
|
2026-02-17 18:33:21 +08:00
|
|
|
/* @phpstan-ignore-next-line */
|
2026-02-15 21:58:42 +08:00
|
|
|
'method' => $frame['function'] ?? null,
|
|
|
|
|
];
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if the class is PackageInstaller
|
|
|
|
|
if (!$this->package_installer_info && is_a($frame['class'], PackageInstaller::class, true)) {
|
|
|
|
|
$this->package_installer_info = [
|
|
|
|
|
'file' => $frame['file'] ?? null,
|
|
|
|
|
'line' => $frame['line'] ?? null,
|
2026-02-17 18:33:21 +08:00
|
|
|
/* @phpstan-ignore-next-line */
|
2026-02-15 21:58:42 +08:00
|
|
|
'method' => $frame['function'] ?? null,
|
2025-11-30 15:35:04 +08:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|