Files
static-php-cli/src/StaticPHP/Exception/SPCException.php

231 lines
7.0 KiB
PHP
Raw Normal View History

2025-11-30 15:35:04 +08:00
<?php
declare(strict_types=1);
namespace StaticPHP\Exception;
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
{
protected bool $simple_output = false;
/** @var null|array Package information */
private ?array $package_info = null;
2025-11-30 15:35:04 +08:00
/** @var null|array Package builder information */
private ?array $package_builder_info = null;
2025-11-30 15:35:04 +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();
}
/**
* 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
{
$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;
}
/**
* Returns package information.
2025-11-30 15:35:04 +08:00
*
* @return null|array{
* package_name: string,
* package_type: string,
* package_class: string,
2025-11-30 15:35:04 +08:00
* file: null|string,
* line: null|int,
* } Package information or null
2025-11-30 15:35:04 +08:00
*/
public function getPackageInfo(): ?array
2025-11-30 15:35:04 +08:00
{
return $this->package_info;
2025-11-30 15:35:04 +08:00
}
/**
* Returns package builder information.
2025-11-30 15:35:04 +08:00
*
* @return null|array{
* file: null|string,
* line: null|int,
* method: null|string,
* } Package builder information or null
2025-11-30 15:35:04 +08:00
*/
public function getPackageBuilderInfo(): ?array
2025-11-30 15:35:04 +08:00
{
return $this->package_builder_info;
2025-11-30 15:35:04 +08:00
}
/**
* Returns package installer information.
2025-11-30 15:35:04 +08:00
*
* @return null|array{
* file: null|string,
* line: null|int,
* method: null|string,
* } Package installer information or null
2025-11-30 15:35:04 +08:00
*/
public function getPackageInstallerInfo(): ?array
2025-11-30 15:35:04 +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;
}
public function isSimpleOutput(): bool
{
return $this->simple_output;
}
public function setSimpleOutput(bool $simple_output = true): void
{
$this->simple_output = $simple_output;
}
/**
* 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;
}
// 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 {
// 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',
/* @phpstan-ignore-next-line */
$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
}
} catch (\Throwable) {
// Ignore reflection errors
2025-11-30 15:35:04 +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,
/* @phpstan-ignore-next-line */
'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,
/* @phpstan-ignore-next-line */
'method' => $frame['function'] ?? null,
2025-11-30 15:35:04 +08:00
];
}
}
}
}