diff --git a/src/Package/Target/php.php b/src/Package/Target/php.php
index 90c68a24..1e1c5930 100644
--- a/src/Package/Target/php.php
+++ b/src/Package/Target/php.php
@@ -45,7 +45,7 @@ use ZM\Logger\ConsoleColor;
#[Target('php-cgi')]
#[Target('php-embed')]
#[Target('frankenphp')]
-class php
+class php extends TargetPackage
{
public static function getPHPVersionID(): int
{
@@ -350,6 +350,9 @@ class php
shell()->cd($package->getSourceDir())
->setEnv($this->makeVars($installer))
->exec("make -j{$concurrency} cli");
+
+ $builder->deployBinary("{$package->getSourceDir()}/sapi/cli/php", BUILD_BIN_PATH . '/php');
+ $package->setOutput('Binary path for cli SAPI', BUILD_BIN_PATH . '/php');
}
#[Stage]
@@ -360,6 +363,9 @@ class php
shell()->cd($package->getSourceDir())
->setEnv($this->makeVars($installer))
->exec("make -j{$concurrency} cgi");
+
+ $builder->deployBinary("{$package->getSourceDir()}/sapi/cgi/php-cgi", BUILD_BIN_PATH . '/php-cgi');
+ $package->setOutput('Binary path for cgi SAPI', BUILD_BIN_PATH . '/php-cgi');
}
#[Stage]
@@ -370,6 +376,9 @@ class php
shell()->cd($package->getSourceDir())
->setEnv($this->makeVars($installer))
->exec("make -j{$concurrency} fpm");
+
+ $builder->deployBinary("{$package->getSourceDir()}/sapi/fpm/php-fpm", BUILD_BIN_PATH . '/php-fpm');
+ $package->setOutput('Binary path for fpm SAPI', BUILD_BIN_PATH . '/php-fpm');
}
#[Stage]
@@ -392,6 +401,7 @@ class php
->exec("make -j{$builder->concurrency} micro");
$builder->deployBinary($package->getSourceDir() . '/sapi/micro/micro.sfx', BUILD_BIN_PATH . '/micro.sfx');
+ $package->setOutput('Binary path for micro SAPI', BUILD_BIN_PATH . '/micro.sfx');
} finally {
if ($phar_patched) {
SourcePatcher::unpatchMicroPhar();
@@ -432,12 +442,17 @@ class php
}
// deploy
$builder->deployBinary($libphp_so, $libphp_so, false);
+ $package->setOutput('Library path for embed SAPI', $libphp_so);
}
// process shared extensions that built-with-php
$increment_files = $diff->getChangedFiles();
foreach ($increment_files as $increment_file) {
$builder->deployBinary($increment_file, $increment_file, false);
+ $files[] = basename($increment_file);
+ }
+ if (!empty($files)) {
+ $package->setOutput('Built shared extensions', implode(', ', $files));
}
// ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=static -------------
@@ -524,6 +539,7 @@ class php
logger()->debug('Patching phpize prefix');
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', "prefix=''", "prefix='" . BUILD_ROOT_PATH . "'");
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', 's##', 's#/usr/local#');
+ $this->setOutput('phpize script path for embed SAPI', BUILD_BIN_PATH . '/phpize');
}
// patch php-config
if (file_exists(BUILD_BIN_PATH . '/php-config')) {
@@ -535,6 +551,7 @@ class php
// move lstdc++ to the end of libs
$php_config_str = preg_replace('/(libs=")(.*?)\s*(-lstdc\+\+)\s*(.*?)"/', '$1$2 $4 $3"', $php_config_str);
FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str);
+ $this->setOutput('php-config script path for embed SAPI', BUILD_BIN_PATH . '/php-config');
}
}
diff --git a/src/StaticPHP/Attribute/Package/PatchBeforeBuild.php b/src/StaticPHP/Attribute/Package/PatchBeforeBuild.php
deleted file mode 100644
index 2343954b..00000000
--- a/src/StaticPHP/Attribute/Package/PatchBeforeBuild.php
+++ /dev/null
@@ -1,11 +0,0 @@
-getVersionWithCommit();
if (!$this->no_motd) {
- echo str_replace('{version}', $version, self::$motd);
+ echo str_replace('{version}', '' . ConsoleColor::none("v{$version}"), '' . ConsoleColor::magenta(self::$motd));
}
}
diff --git a/src/StaticPHP/Command/BuildTargetCommand.php b/src/StaticPHP/Command/BuildTargetCommand.php
index e66f514b..2756070b 100644
--- a/src/StaticPHP/Command/BuildTargetCommand.php
+++ b/src/StaticPHP/Command/BuildTargetCommand.php
@@ -51,6 +51,8 @@ class BuildTargetCommand extends BaseCommand
$this->output->writeln("✔ BUILD SUCCESSFUL ({$usedtime} s)");
$this->output->writeln("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
+ $installer->printBuildPackageOutputs();
+
return static::SUCCESS;
}
}
diff --git a/src/StaticPHP/DI/CallbackInvoker.php b/src/StaticPHP/DI/CallbackInvoker.php
index f14f9468..fa11a7f1 100644
--- a/src/StaticPHP/DI/CallbackInvoker.php
+++ b/src/StaticPHP/DI/CallbackInvoker.php
@@ -26,6 +26,10 @@ class CallbackInvoker
* 4. Default value
* 5. Null (if nullable)
*
+ * Note: For object values in context, the invoker automatically registers
+ * the object under all its parent classes and interfaces, allowing type hints
+ * to match any type in the inheritance hierarchy.
+ *
* @param callable $callback The callback to invoke
* @param array $context Context parameters (type => value or name => value)
*
@@ -35,6 +39,9 @@ class CallbackInvoker
*/
public function invoke(callable $callback, array $context = []): mixed
{
+ // Expand context to include all parent classes and interfaces for objects
+ $context = $this->expandContextHierarchy($context);
+
$reflection = new \ReflectionFunction(\Closure::fromCallable($callback));
$args = [];
@@ -95,4 +102,43 @@ class CallbackInvoker
'void', 'null', 'false', 'true', 'never',
], true);
}
+
+ /**
+ * Expand context to include all parent classes and interfaces for object values.
+ * This allows type hints to match any type in the object's inheritance hierarchy.
+ *
+ * @param array $context Original context array
+ * @return array Expanded context with all class hierarchy mappings
+ */
+ private function expandContextHierarchy(array $context): array
+ {
+ $expanded = [];
+
+ foreach ($context as $key => $value) {
+ // Keep the original key-value pair
+ $expanded[$key] = $value;
+
+ // If value is an object, add mappings for all parent classes and interfaces
+ if (is_object($value)) {
+ $reflection = new \ReflectionClass($value);
+
+ // Add concrete class
+ $expanded[$reflection->getName()] = $value;
+
+ // Add all parent classes
+ while ($parent = $reflection->getParentClass()) {
+ $expanded[$parent->getName()] = $value;
+ $reflection = $parent;
+ }
+
+ // Add all interfaces
+ $interfaces = (new \ReflectionClass($value))->getInterfaceNames();
+ foreach ($interfaces as $interface) {
+ $expanded[$interface] = $value;
+ }
+ }
+ }
+
+ return $expanded;
+ }
}
diff --git a/src/StaticPHP/Package/Package.php b/src/StaticPHP/Package/Package.php
index 0317e1bc..6cad1fab 100644
--- a/src/StaticPHP/Package/Package.php
+++ b/src/StaticPHP/Package/Package.php
@@ -23,6 +23,9 @@ abstract class Package
/** @var array $build_functions Build functions for different OS binding */
protected array $build_functions = [];
+ /** @var array */
+ protected array $outputs = [];
+
/**
* @param string $name Name of the package
* @param string $type Type of the package
@@ -69,6 +72,17 @@ abstract class Package
return $ret;
}
+ public function setOutput(string $key, string $value): static
+ {
+ $this->outputs[$key] = $value;
+ return $this;
+ }
+
+ public function getOutputs(): array
+ {
+ return $this->outputs;
+ }
+
/**
* Add a build function for a specific platform.
*
diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php
index 9770a737..96316887 100644
--- a/src/StaticPHP/Package/PackageInstaller.php
+++ b/src/StaticPHP/Package/PackageInstaller.php
@@ -104,6 +104,16 @@ class PackageInstaller
return $this;
}
+ public function printBuildPackageOutputs(): void
+ {
+ foreach ($this->build_packages as $package) {
+ if (($outputs = $package->getOutputs()) !== []) {
+ InteractiveTerm::notice('Package ' . ConsoleColor::green($package->getName()) . ' outputs');
+ $this->printArrayInfo(info: $outputs);
+ }
+ }
+ }
+
/**
* Run the package installation process.
*/
diff --git a/src/StaticPHP/Registry/PackageLoader.php b/src/StaticPHP/Registry/PackageLoader.php
index 15ff0f4a..0ef3fb8e 100644
--- a/src/StaticPHP/Registry/PackageLoader.php
+++ b/src/StaticPHP/Registry/PackageLoader.php
@@ -12,7 +12,6 @@ use StaticPHP\Attribute\Package\Extension;
use StaticPHP\Attribute\Package\Info;
use StaticPHP\Attribute\Package\InitPackage;
use StaticPHP\Attribute\Package\Library;
-use StaticPHP\Attribute\Package\PatchBeforeBuild;
use StaticPHP\Attribute\Package\ResolveBuild;
use StaticPHP\Attribute\Package\Stage;
use StaticPHP\Attribute\Package\Target;
@@ -166,16 +165,14 @@ class PackageLoader
if ($refClass->getParentClass() !== false) {
if (is_a($class_name, Package::class, true)) {
self::$packages[$attribute_instance->name] = new $class_name($attribute_instance->name, $package_type);
- $instance_class = self::$packages[$attribute_instance->name];
}
}
- if (!isset($instance_class)) {
- $instance_class = $refClass->newInstance();
- }
-
$pkg = self::$packages[$attribute_instance->name];
+ // Use the package instance if it's a Package subclass, otherwise create a new instance
+ $instance_class = is_a($class_name, Package::class, true) ? $pkg : $refClass->newInstance();
+
// validate package type matches
$pkg_type_attr = match ($attribute->getName()) {
Target::class => ['target', 'virtual-target'],
@@ -204,18 +201,13 @@ class PackageLoader
// #[Stage('stage_name')]
Stage::class => self::addStage($method, $pkg, $instance_class, $method_instance),
// #[InitPackage] (run now with package context)
- InitPackage::class => ApplicationContext::invoke([$instance_class, $method->getName()], [
- Package::class => $pkg,
- $pkg::class => $pkg,
- ]),
+ InitPackage::class => ApplicationContext::invoke([$instance_class, $method->getName()], ['package' => $pkg]),
// #[InitBuild]
ResolveBuild::class => $pkg instanceof TargetPackage ? $pkg->setResolveBuildCallback([$instance_class, $method->getName()]) : null,
// #[Info]
Info::class => $pkg->setInfoCallback([$instance_class, $method->getName()]),
// #[Validate]
Validate::class => $pkg->setValidateCallback([$instance_class, $method->getName()]),
- // #[PatchBeforeBuild]
- PatchBeforeBuild::class => $pkg->setPatchBeforeBuildCallback([$instance_class, $method->getName()]),
default => null,
};
}
@@ -224,6 +216,7 @@ class PackageLoader
self::$packages[$pkg->getName()] = $pkg;
}
+ // For classes without package attributes, create a simple instance for non-package stage callbacks
if (!isset($instance_class)) {
$instance_class = $refClass->newInstance();
}